Thinking House Lab
DE

Sprache

  • English
  • Deutsch
  • Français · folgt
  • Español · folgt
  • 中文 · folgt

Power Flow Logic · Folge 3 · Stufe 3

Netzladen: wann und wie viel

Der Planer aus Folge 3 als kleines, lesbares Python (eine freie, weit verbreitete Programmiersprache): eine Woche Ladestand, Stunde für Stunde, die Lücke unter einer Marke und die billigsten bekannten Viertelstunden, eine nach der anderen. Die Beispielwoche aus dem Video ist ein Test, den du laufen lassen kannst.

NachbauenKostenlos15 Min. Lesezeit

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

Der Ladeplan auf einen Blick
FrageAntwort
Was es istEin 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 rechnetAlle 15 Minuten und sofort, wenn neue Preise kommen. Preise sind etwa 10 bis 35 Stunden voraus bekannt; die Akku-Prognose reicht 168 Stunden.
WarumDie 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 GrenzenBoden 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ängeLadestand 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ängeDie 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.
NetzpreisZä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

Während der Akku lädt: ein Weg für den Strom
Strom-netzLadegerätAkkuWechsel-richter-systemHaus

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.

Ladestand ohne Kauf und mit Plan
Preise bekannt20 %30 %40 %50 %MiDoFrSaSoMoDi
  • 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.

Die 33 Stunden mit bekannten Preisen: Preis je Stunde und die gekauften Viertelstunden
10 ct20 ct30 ct40 ctDi 18:00Mi 00:0006:0012:0018:00
  • 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.

Was der Code für die Beispielwoche rechnet
Ergebnis
Preise bekanntbis Mittwoch Mitternacht, im Mittel 31,8 ct
Ohne Kaufam Mittwoch um 7 Uhr unter 30 %, ab 17 Uhr am Boden
Lücke22,5 Punkte bis zum letzten bekannten Preis
Bedarf23 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)
Vorrat5 Viertelstunden spät am Mittwochabend (21,3 bis 22,4 ct)
Zusammen28 Punkte, gut ein Viertel des Akkus, im Mittel für etwa 22,5 ct
Mit Planzwischen 30,2 und 50,4 % innerhalb der bekannten Preise
Restab 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.

Die Woche, Stunde für Stunde: simulate()src/ladeplan/simulation.py · Zeilen 73–94
def 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. change ist Sonne minus Bedarf minus Verluste plus das, was in dieser Stunde geladen wird.
  • Heraus kommen zwei Stände: levels, gehalten zwischen Boden und 100 %, und raw, nur oben gehalten. Unter dem Boden fällt raw weiter um das, was das Haus noch bräuchte; dadurch wird eine Lücke messbar.
  • at_floor_pp ist der ungeplante Nachkauf am Boden, spilled_pp der Solarstrom, der bei 100 % nirgendwohin kann.
Marke und Lücke: mark_pct(), first_gap(), gap_pp()src/ladeplan/marke.py · Zeilen 38–66
def 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_day je 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 mit raw, damit eine lange Strecke am Boden voll zählt.
Der Plan: make_plan()src/ladeplan/einkauf.py · Zeilen 195–311
def 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_ok immer wahr), Vorrat mit clearly_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:

Beispiel und Tests
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 passed

Das Beispiel gibt jeden Schritt der Woche aus, hier gekürzt (Ausgabe auf Englisch):

Ausgabe von 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:

Fälle zum Nachrechnen
#FallErwartet
122 % zu Beginn, das Haus braucht 5 Punkte je Stundebleibt bei 20 %; 3, dann 5 Punkte ungeplant am Boden
2die Marke in Stunde 0, 48, 72 und 14430, 32, 34 und 40 %
320 ct vor der ersten Lücke, 5 ct dahinterzuerst 20 ct; 5 ct erst, wenn die Lücke dort ankommt
4eine Lücke innerhalb der bekannten Preise, jede Stunde 80 ctwird trotzdem gekauft
5Akku bei 89,5 %, die billigsten Stunden gleich am Anfangübersprungen: Sie würden über 90 % enden
6Vorrat mit 24, 26, 30 und 40 ct bekanntnur 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.

Die Beispielwoche als Test: 23 + 5 Viertelstundentests/test_example_week.py · Zeilen 53–57
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)
  • week ist eine Fixture: pytest rechnet die Beispielwoche einmal und gibt den Plan jedem Test in der Datei.
  • pytest.approx vergleicht 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.

Eingänge je Stunde
NameEinheitBedeutungTypischer BereichKommt von
start_pct%Ladestand jetztBoden … 100ein Messwert, dem du vertraust, frisch
solar_ppPunkte/hWas die Sonne bringt; None = unbekannt, in dieser Stunde wird nichts gekauft0 nachts … mehrere Punkte mittagsdeine Solarprognose
demand_ppPunkte/hAlles, was das Haus verbraucht: Muster, Wärmepumpe, Auto, Standbyetwa 1 … 2,5 im Wintergelernte Muster und Wärmepumpen-Prognose
losses_ppPunkte/hWandlungsverluste: Dauerverbrauch plus Anteil je Richtungetwa 0,1 … 0,3gemessen oder geschätzt
prices_ctct/kWhPreis je Viertelstunde, None = noch unbekanntetwa −5 … 60dein dynamischer Tarif
Einstellungen und Ergebnisse
NameEinheitBedeutungTypischer BereichKommt von
floor_pct%Tiefster erlaubter Ladestand10 … 30 (Beispiel 20)Einstellung
buffer_ptsPunktePuffer über dem Boden5 … 15 (Beispiel 10)Einstellung
step_pts_per_dayPunkteZuschlag je Tag ab Stunde 480 … 4 (Beispiel 2)Einstellung; am besten aus Prognosefehlern gelernt
charge_pp_per_quarterPunkteWas eine Ladeviertelstunde bringtBeispiel 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_hourUhrzeitTägliche Stunde, die der Sonne gehörtBeispiel 15Einstellung
reserve_max_ctct/kWhKlar billig: höchstens dieser PreisBeispiel 25Einstellung
reserve_percentile_ctct/kWhKlar billig: 30. Perzentil aus 14 TagenBeispiel 27,7percentile() deines Preisverlaufs
gap_ppPunkteLücke bis zum letzten bekannten Preis, ohne Kauf0 …make_plan()
target_pct%„Lade bis …“ am Ende eines LadeblocksBoden … 90Plan.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_pp weglassen. Ein fester Tarif: Es gibt nichts zu planen, jede Viertelstunde kostet gleich viel.
  • Nur Stundenpreise: jeden Preis viermal wiederholen. Noch kein Preisverlauf: reserve_percentile_ct auf None lassen; 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

Links geprüft am 13. Oktober 2026.

Deine Erfahrung zählt

Welches System läuft bei dir, und wo hakt es? Ich freue mich über deine Erfahrungen, Bedenken und Ideen in den Kommentaren unter den Videos. Gute Ideen greife ich auf und setze sie öffentlich um: als eigenes Video oder als Ergänzung zu einem bestehenden.

Zum Kanal