Optimizer

Which solver decides what your battery does next, how finely it plans, and the limits it has to stay inside.

What this is for

  • I want the battery to charge when power is cheap and carry me through the expensive hours.
  • I want to choose between the solver built into EOS Connect and one I already run myself.
  • I want to keep the plan inside the import and export limits my grid operator gave me.

Everything on this page sits under eos.*. What the optimizer is given — prices, forecasts, measured load — is configured on the other pages in this section.

Choosing an optimizer

The optimizer decides what your battery does next. Set it in eos.source.

local_evopt

The default. A mixed-integer solver running inside EOS Connect. Nothing else to install or keep alive.

eos_server

Akkudoktor EOS, running separately. The most detailed house model, and the only backend that uses the outside-temperature forecast.

evopt

An external EVopt server. Lightweight, fast, and useful if you already run one.

Tuning the built-in optimizer

Three settings shape its behaviour, and all have sensible defaults:

  • eos.local_evopt_charging_strategy — default charge_before_export: fill the battery before selling surplus to the grid. Alternatives are discharge_before_import, maximize_self_consumption, attenuate_grid_peaks and none.
  • eos.local_evopt_discharging_strategy — default discharge_before_import: use stored energy before buying. Or emergency_reserve, or none.

eos.local_evopt_terminal_soc_value — default cheapest_ahead: decides what charge still in the battery when the planning horizon runs out is worth. This matters more than it sounds. The optimizer plans a fixed window and then stops, so whatever it thinks the leftover charge is worth is what decides whether it saves energy for tomorrow or spends it tonight.

  • cheapest_ahead values it at the lowest electricity price in the horizon. Charging and discharging both lose a little to conversion, so this leaves a band around that price in which the battery simply holds: nothing is cheap enough to be worth buying just to store, and nothing near the bottom of the tariff is dear enough to be worth emptying into. It fits the way the planning window is cut — it always ends at midnight, and midnight is always followed by the cheapest hours of the night, so charge left over at the end can be bought back shortly afterwards for about the same price. On a day with cheap grid power available throughout, that means the battery is expected to end close to empty, and that is the correct answer rather than a fault.
  • stored_price is the old behaviour, kept so you can go back: leftover charge is valued at what it cost to put there. On a mostly solar-charged battery that is close to nothing, so the optimizer treats a full battery at the end of the horizon as near-worthless and empties it — even into cheap hours, and even when it means exporting solar surplus for the feed-in tariff rather than keeping it.

The external EVopt server runs the same engine and is sent the same figure, so eos.external_evopt_terminal_soc_value does the same job when eos.source is evopt. Only the slots genuinely ahead are considered — in quarter-hourly mode the forecast wraps around, and yesterday's prices trailing the end of it are not offers you can still take.

Set eos.local_evopt_emergency_reserve_pct above 0 to hold a hard floor of charge in reserve at the end of the planning horizon, for outage cover. It is a floor, not an economic signal — deciding how much charge is worth keeping is eos.local_evopt_terminal_soc_value’s job.

If your grid connection is limited, cap it explicitly with eos.local_evopt_max_grid_import_w and eos.local_evopt_max_grid_export_w. Both default to 0, meaning "use the inverter's own limit". eos.local_evopt_num_threads and eos.local_evopt_time_limit bound the CBC solver on slow hardware; 0 means no limit.

The external-EVopt equivalents, eos.external_evopt_max_grid_import_w and eos.external_evopt_max_grid_export_w, ship with a default of 10000 W, not 0. The fallback to the inverter limit therefore never fires unless you set them to 0 yourself.

Planning resolution

eos.time_frame sets how finely the day is sliced: 3600 seconds (hourly, the default) or 900 (quarter-hourly). Quarter-hourly follows a volatile tariff more closely and costs more computation. Changing it needs a restart.

Dynamic PV override

Set eos.dyn_override_discharge_allowed_pv_greater_load to allow discharging in any slot where forecast solar exceeds forecast load, even if the optimizer planned to hold. It stops the battery sitting idle through a bright but intermittently cloudy afternoon that the plan treated as one flat block. Off by default.

It never overrides a slot where grid charging was requested, and a manual override you set in the dashboard always wins over it.

The rest of the optimizer settings

Timeouts, solver limits, and the import and export ceilings your grid operator sets — the last of these are explained under Grid import and export limits.