Market & Sessions¶
The Market, also called the Internal Market or Local Energy Market (LEM), is arguably
RECreation's most important tool. It generates transactions between members, using the
information currently uploaded, for every available session (status OPEN).
Because it is computationally expensive, the Market is an independent module. It is also the first operation to run, which makes it the front line for error checking: most problems with a community's configuration surface here, and only rarely in the Settlement module that runs afterwards.
Start here when something fails
If a community does not behave as expected, run the Market first. Missing meter data, incomplete contracts or opportunity costs, and bilateral prices that do not cover every session are all reported at this stage, before settlement is ever attempted.
flowchart LR
A[Community configuration<br/>and uploaded data] --> B[Market<br/>clearing]
B -->|Transactions| C[Settlement]
What is a Session?¶
A Session corresponds to one Interval Settlement Period (ISP) — a 15-minute interval aligned with smart meter reading resolution and OMIE's market time granularity. Sessions are the atomic unit for market clearing and settlement.
Session Lifecycle¶
%%{init: {"themeVariables": {"fontSize": "16px"}, "flowchart": {"nodeSpacing": 60, "rankSpacing": 120, "curve": "basis"}}}%%
flowchart LR
S(( )) -->|Session created| OPEN
OPEN -->|Market clearing runs| CLEARED
CLEARED -->|Settlement computed| SETTLED
SETTLED --> E(( ))
CLEARED -.->|Reset| OPEN
SETTLED -.->|Reset| OPEN
Dashed arrows are resets: see When Sessions Reset.
| Status | Description |
|---|---|
OPEN |
Session is active; meter data can be uploaded |
CLEARED |
Market has cleared; transactions recorded |
SETTLED |
Financial settlement computed for all members |
When Sessions Reset¶
A CLEARED or SETTLED session goes back to OPEN whenever something it was computed from
changes after the fact. RECreation does this automatically so that results never disagree with
the data behind them. Only the sessions in the affected period are reset.
When a session resets:
- Its transactions, offers, allocations and settlement outputs are deleted.
- Calculated opportunity costs for those sessions are deleted too, and are calculated again at the next market run.
- Uploaded meter measurements and uploaded opportunity costs are kept.
- You must run market clearing, and then settlement, again for those dates.
Typical examples:
| What you change | Sessions that reset |
|---|---|
Upgrade a battery's max_power from 5 kW to 10 kW with start_date: 2026-01-14, after the market already ran for January |
Sessions from January 14 onward. January 1 to 13 keep the 5 kW version and are not affected |
| Correct a contract's tariff cycle or contracted power for a past month | Every session in the period the correction covers |
End a meter or resource with DELETE ...?effective_at=2026-06-01 when June sessions were already cleared |
Sessions from June 1 onward |
| Change a member's ownership share of a resource, or when a member joins or leaves, for a past period | Sessions in that period |
Mark a CPE pair as is_active: false in RESP for a past period |
Sessions in that period, since the self-consumption grid-access tariff on their transactions changes |
| Change a fixed bilateral price for a past period | Sessions in that period |
| Upload opportunity costs for past intervals | The sessions those intervals belong to |
| Upload or correct meter loads, DSO data, allocation coefficients or per-interval bilateral prices | The sessions for the timestamps contained in the upload |
| Settlement runs after the structure changed since market clearing | The affected sessions are reopened for market clearing instead of being settled |
Future changes
A change that only applies from a future date does not touch sessions that already exist for earlier dates. Only retroactive changes reset sessions.
See Business Validity for how dated changes work and how a market run splits into windows when the structure changes part-way through.
Local Energy Market (LEM)¶
In the LEM, members with surplus energy (producers/prosumers) trade with members needing energy (consumers). The market clearing algorithm matches offers and determines:
- Who buys from whom
- The clearing price (€/kWh)
- The traded quantity (kWh)
Pricing¶
The internal trading price is derived from the community's market_rate:
| Rate | Price Derivation |
|---|---|
VANILLA_INTERMEDIATE_MARKET_RATE |
price = OMIE day-ahead spot price × market_divisor |
VANILLA_SDR |
price derived from supply-demand ratio within the community |
VANILLA_CV |
price derived from crossing value of buy/sell curves |
FIXED_PRICE |
fixed value set in community configuration |
BILATERAL_PRICE |
agreed bilaterally between members before market clearing, with a price for each 15-minute interval — see Per-Interval Bilateral Prices |
FIXED_BILATERAL_PRICE |
agreed bilaterally between members, with one dated price per provider → receiver pair — see Fixed Bilateral Prices |
TREE_DNAC |
dynamic allocation coefficients computed via decision-tree algorithm; price determined by the bilateral agreement between Promoter and each receiver, set per 15-minute interval |
FIXED_TREE_DNAC |
same as TREE_DNAC, but the Promoter–receiver price is one dated fixed price per pair |
Contract dependency
Mechanisms that reference OMIE prices or retailer rates (e.g. VANILLA_INTERMEDIATE_MARKET_RATE,
INDEXED) require valid contract information for each CPE.
Opportunity Costs¶
An opportunity cost is what a CPE would pay or earn if it dealt with its retailer instead of the community, for one 15-minute interval. Together the two values bound the internal price: a member would never buy locally above what the retailer charges, nor sell locally below what the retailer pays for surplus.
| Field | Required | Description |
|---|---|---|
datetime_timestamp |
✅ | Start of the 15-minute interval (Portuguese timezone) |
meter_id |
✅ | UUID of the CPE the opportunity cost refers to |
l_buy |
✅ | Price at which the CPE would buy energy from its supplier (€/kWh). Upper reference bound for the internal price |
l_sell |
✅ | Price at which the CPE would sell its surplus to its supplier (€/kWh). Lower reference bound for the internal price |
Market clearing needs an opportunity cost for every CPE and every interval, and it gets them in one of two ways:
- Calculated from contracts. When the CPE's contract has tariff values
(retailer prices, grid-access tariffs and OMIE prices where indexed), the platform computes
l_buyandl_sellitself. Nothing needs to be uploaded. - Uploaded by the integrator. When retailer pricing cannot be shared (contract
buy_typeandsell_typeset toNO_VALUES), upload the values withPOST /opportunity_costs. The upload must cover every session and every CPE in the period being cleared; otherwise market clearing is refused with an error saying the uploaded opportunity costs do not cover all sessions and meters.
Uploaded values take precedence: if any are present for the period, the platform uses them instead of calculating its own. Calculated values are stored for reference and are overwritten the next time the market runs, whereas uploaded ones are kept.
Uploads reopen sessions
Uploading opportunity costs for past intervals invalidates the market sessions they
touch: the session goes back to OPEN and its transactions and settlement outputs are
deleted. Run market clearing and settlement again for those dates.
Opportunity costs and KPIs
Opportunity costs are enough to clear the market, but without contract pricing the cost-related KPIs, such as savings against individual self-consumption, are not available.
Bilateral Prices¶
With a bilateral market rate, the price is not derived from the market: each provider →
receiver member pair has a price agreed beforehand and supplied by the integrator. There are
two ways to supply it. Which one is used depends on the community's market_rate:
| Fixed | Per-interval | |
|---|---|---|
| Market rates | FIXED_BILATERAL_PRICE, FIXED_TREE_DNAC |
BILATERAL_PRICE, TREE_DNAC |
| Price per pair | One price, applied to every interval of the period it covers | A separate price for each 15-minute interval |
| Time dimension | Dated: start_date / end_date, like other versioned entities |
Time series: each row has its own datetime_timestamp |
| Changing the price | Send a new price for [start_date, end_date]; earlier history is kept |
Upload rows for the intervals that change |
| Endpoints | /bilateral-prices/fixed |
/bilateral-prices |
| Coverage required | At least one provider/receiver price pair | At least one pair for every session being cleared |
| Best for | Stable agreements, such as a Promoter selling at 80 €/MWh for the whole contract | Prices that vary through the day or over time, such as peak and off-peak rates |
Only the prices of the community's current market rate are used. Prices uploaded for the other mode are ignored.
Fixed Bilateral Prices¶
Fixed bilateral prices set one price (€/kWh) for a provider → receiver member pair, applied to every interval of the period it covers. They are dated:
| Field | Required | Description |
|---|---|---|
provider_user_id |
✅ | UUID of the selling member |
receiver_user_id |
✅ | UUID of the buying member |
price |
✅ | Price in €/kWh |
start_date |
✅ | First day (inclusive, Europe/Lisbon) on which this price applies |
end_date |
— | Last day (inclusive) on which this price applies. null = open-ended |
POST /bilateral-prices/fixedcreates prices;GET /bilateral-prices/fixedlists them with their periods.PUT /bilateral-prices/fixed/{receiver_user_id}/{provider_user_id}sets a new price for[start_date, end_date], keeping the price outside that period.DELETE /bilateral-prices/fixed/{receiver_user_id}/{provider_user_id}?effective_at=YYYY-MM-DDremoves the price from that day. Add&end_date=YYYY-MM-DDto remove only that period.
See Business Validity.
Per-Interval Bilateral Prices¶
Per-interval bilateral prices let the price of a provider → receiver pair change from one 15-minute interval to the next. They are a time series.
| Field | Required | Description |
|---|---|---|
datetime_timestamp |
✅ | Start of the 15-minute interval the price applies to |
provider_user_id |
✅ | UUID of the selling member |
receiver_user_id |
✅ | UUID of the buying member |
price |
✅ | Price in €/kWh |
POST /bilateral-pricesuploads a list of rows. Processing runs in the background and the response returns atask_idto poll.GET /bilateral-prices/by_providerreturns a provider's prices over a date range, andGET /bilateral-prices/paginationlists the rows with filters.- Market clearing is refused unless every session being cleared has at least one provider → receiver price. If members may act as either producer or consumer in an interval, provide prices for all possible pairs.
Choosing between them
If the price for a pair never changes within a period, use the fixed form: it is one record instead of one per interval, and changing it later is a single dated update. Use per-interval prices only when the price actually varies by interval.
Transactions¶
Each cleared trade generates a Transaction record with:
| Field | Description |
|---|---|
datetime_timestamp |
Start of the 15-minute ISP interval (Portuguese timezone) |
provider_resource_id |
Resource selling energy |
receiver_resource_id |
Resource buying energy |
provider_user_id |
Member who owns the selling resource |
receiver_user_id |
Member who owns the buying resource |
provider_meter_id |
DSO meter behind the selling resource |
receiver_meter_id |
DSO meter behind the buying resource |
transaction_type |
INTRA_CPE or PUBLIC_CPE |
price |
Price before grid-access tariffs (€/kWh) |
energy |
Energy traded (kWh) |
Transaction Types¶
| Type | Description |
|---|---|
INTRA_CPE |
Both resources are behind the same DSO meter. No self-consumption grid-access tariff applies |
PUBLIC_CPE |
Resources are behind different DSO meters connected through the public grid (RESP). The buyer is charged the self-consumption grid-access tariff |
Ownership and compensation
If a transaction occurs between two resources with fractional ownership, the resulting compensation — whether paid or received — is allocated to each resource's owners in proportion to their ownership shares.