Skip to content

Environments

The built-in environment classes, exported from sekai.envs and registered under the IDs in the catalog. Create them with sekai.make(id), or instantiate the classes directly (no time limit is applied then).

Classic control

See Classic control.

CartPoleEnv

CartPoleEnv(sutton_barto_reward: bool = False)

Bases: Env[NDArray[float32], intp]

Balance a pole on a cart by pushing the cart left or right.

The cart-pole problem of Barto, Sutton & Anderson (1983), with dynamics identical to gymnasium's CartPole-v1.

Observation (Box, shape (4,)): cart position, cart velocity, pole angle (rad), pole angular velocity.

Action (Discrete(2)): 0 pushes the cart left, 1 pushes it right.

Reward: +1 for every step, including the terminating one. With sutton_barto_reward=True: 0 per step and -1 on termination.

Start: every state variable uniform in [-0.05, 0.05]; override with reset(options={"low": ..., "high": ...}).

Termination: |pole angle| > 12 degrees or |cart position| > 2.4. Truncation: registered as CartPole-v1 with max_episode_steps=500.

MountainCarEnv

MountainCarEnv(goal_velocity: float = 0.0)

Bases: Env[NDArray[float32], intp]

Drive an under-powered car up a hill by building momentum.

Moore (1990); dynamics identical to gymnasium's MountainCar-v0.

Observation (Box, shape (2,)): position in [-1.2, 0.6], velocity in [-0.07, 0.07].

Action (Discrete(3)): 0 accelerate left, 1 no acceleration, 2 accelerate right.

Reward: -1 per step.

Start: position uniform in [-0.6, -0.4], velocity 0; override the position range with reset(options={"low": ..., "high": ...}).

Termination: position >= 0.5 (and velocity >= goal_velocity). Truncation: registered as MountainCar-v0 with max_episode_steps=200.

height staticmethod

height(xs: Any) -> Any

Height of the track at position xs (used for rendering).

ContinuousMountainCarEnv

ContinuousMountainCarEnv(goal_velocity: float = 0.0)

Bases: Env[NDArray[float32], NDArray[float32]]

Mountain car with a continuous force action.

Dynamics identical to gymnasium's MountainCarContinuous-v0 (including its float32 state after the first step).

Observation (Box, shape (2,)): position in [-1.2, 0.6], velocity in [-0.07, 0.07].

Action (Box(-1, 1, (1,))): force, clipped to [-1, 1] and scaled by power=0.0015.

Reward: -0.1 * action**2 per step (unclipped action), +100 on reaching the goal.

Start: position uniform in [-0.6, -0.4], velocity 0.

Termination: position >= 0.45 (and velocity >= goal_velocity). Truncation: registered as MountainCarContinuous-v0 with max_episode_steps=999.

height staticmethod

height(xs: Any) -> Any

Height of the track at position xs (used for rendering).

PendulumEnv

PendulumEnv(g: float = 10.0)

Bases: Env[NDArray[float32], NDArray[float32]]

Swing a pendulum up and keep it upright with limited torque.

Dynamics identical to gymnasium's Pendulum-v1.

Observation (Box, shape (3,)): cos(theta), sin(theta), angular velocity in [-8, 8].

Action (Box(-2, 2, (1,))): torque.

Reward: -(theta**2 + 0.1 * theta_dot**2 + 0.001 * torque**2), theta normalised to [-pi, pi] with 0 upright. Range [-16.27, 0].

Start: theta uniform in [-pi, pi], theta_dot uniform in [-1, 1]; override with reset(options={"x_init": ..., "y_init": ...}) (symmetric bounds).

No termination. Truncation: registered as Pendulum-v1 with max_episode_steps=200.

AcrobotEnv

AcrobotEnv()

Bases: Env[NDArray[float32], intp]

Swing the tip of a two-link pendulum above a line by torquing the middle joint.

Sutton (1996); dynamics identical to gymnasium's Acrobot-v1 ("book" equations, RK4 integration with dt=0.2).

Observation (Box, shape (6,)): cos/sin of both joint angles and both angular velocities (in [-4pi, 4pi] and [-9pi, 9pi]).

Action (Discrete(3)): torque -1, 0 or +1 on the actuated joint.

Reward: -1 per step, 0 on the terminating step.

Start: every state variable uniform in [-0.1, 0.1].

Termination: -cos(theta1) - cos(theta1 + theta2) > 1. Truncation: registered as Acrobot-v1 with max_episode_steps=500.

Renderers

Exported from sekai.envs.classic_control. Each render(env) returns an (H, W, 3) uint8 frame.

CartPoleRenderer

CartPoleRenderer(width: int = 600, height: int = 400)

Bases: BaseRenderer

MountainCarRenderer

MountainCarRenderer(width: int = 600, height: int = 400)

Bases: BaseRenderer

Draws MountainCar-v0 and MountainCarContinuous-v0.

PendulumRenderer

PendulumRenderer(size: int = 500)

Bases: BaseRenderer

AcrobotRenderer

AcrobotRenderer(size: int = 500)

Bases: BaseRenderer

Finance

See Finance.

TradingEnv

TradingEnv(prices: ArrayLike | None = None, *, data: MarketData | None = None, episode_length: int = 252, lookback: int = 30, positions: Sequence[float] = (-1.0, 0.0, 1.0), transaction_cost: float | CostModel = 0.001, mu: float = 0.05, sigma: float = 0.2, dt: float | None = None, s0: float = 100.0, model: MarketModel | None = None, reward: RewardFunction | None = None, sampler: str | int | EpisodeSampler = 'random', capital: float = 1000000.0)

Bases: Env[NDArray[float32], intp]

Trade a single asset by choosing a target position each step.

Prices are simulated (geometric Brownian motion by default, or any single-asset model from sekai.finance.simulators), or drawn as windows from a history you provide: raw prices or a one-asset MarketData as data.

  • Observation: Box of shape (lookback + 1,): the last lookback log returns followed by the current position (drifted with the price, see below).
  • Action: Discrete(len(positions)), an index into positions, the target position as a fraction of equity. The default (-1, 0, 1) is short / flat / long; use (0, 1) for long-only.
  • Dynamics: trading from the current position p to the target w costs cost (by default transaction_cost * |w - p|), then the position earns the asset's return r and drifts with the price, as a portfolio of the asset and cash does: p <- w (1 + r) / (1 + w r). Holding a long (w = 1) or flat position is free; holding a short or levered one trades the drift back.
  • Reward: by default the log growth of equity over the step, log((1 - cost) * (1 + w * asset_return)), summing to the log of final over initial equity. Pass reward= (a sekai.finance.RewardFunction) to optimise something else, e.g. DifferentialSharpe().
  • Termination: equity falls to zero or below (only possible with leverage or extreme moves). Truncation: after episode_length steps (an episode is a window of an ongoing market, so the observation holds no clock).
  • info: equity, position (drifted), price, asset_return, cost, turnover (|w - p|), timestamp (the decision time: a date from dated data, else the row index) and dt (step length in years).

With a history, reset(options={"start": t}) begins the episode with its first decision at row t (an int) or timestamp t (a date, or sekai.finance.Time(t) for a numeric timestamp); otherwise sampler picks the window: "random" (default), "sequential" (consecutive windows, for evaluation), an int (a fixed window) or a sekai.finance.EpisodeSampler.

step() after the episode has ended raises ResetRequired.

Parameters:

Name Type Description Default
prices ArrayLike | None

Historical prices: a 1-D array, a pandas Series or a one-column DataFrame. Pandas input indexed by dates keeps them (as data would); with another index its rows are numbered from 0.

None
data MarketData | None

A one-asset MarketData (dated rows, and fields such as volume and spread for cost models). Pass prices, data or model, not several.

None
episode_length int

Steps per episode.

252
lookback int

Log returns in the observation.

30
positions Sequence[float]

Allowed target positions.

(-1.0, 0.0, 1.0)
transaction_cost float | CostModel

A proportional cost rate, or any sekai.finance.CostModel.

0.001
mu float

Annualised GBM drift for the default simulation.

0.05
sigma float

Annualised GBM volatility for the default simulation.

0.2
dt float | None

Step length in years (default: implied by dated MarketData timestamps, else the model's dt, else 1/252).

None
s0 float

Initial simulated price.

100.0
model MarketModel | None

A one-asset market model to simulate instead of GBM.

None
reward RewardFunction | None

Reward function (default LogReturn()). The env keeps a copy (as it does of a reward_fn set later), so a stateful reward is never shared.

None
sampler str | int | EpisodeSampler

How episodes are placed in the history.

'random'
capital float

Account size in currency, for cost models that need it.

1000000.0

reward_fn property writable

reward_fn: RewardFunction

The reward function. Setting it stores a copy, as reward= does, so a stateful reward set on several envs at once (e.g. with a vector env's set_attr) is not shared.

episode_length property writable

episode_length: int

Steps per episode (read-only: it sizes the price windows).

lookback property writable

lookback: int

Log returns in the observation (read-only: it sizes the observation).

price property

price: float

The current asset price.

elapsed_steps property

elapsed_steps: int

Steps taken in the current episode.

timestamp property

timestamp: Any

The time of the current decision, as in info["timestamp"] (the step when simulated).

price_history property

price_history: NDArray[float64]

The prices observed so far, lookback window included, ending at the current price.

PortfolioEnv

PortfolioEnv(prices: ArrayLike | None = None, *, data: MarketData | None = None, n_assets: int | None = None, episode_length: int = 252, lookback: int = 30, transaction_cost: float | CostModel = 0.001, allow_short: bool = False, mu: ArrayLike | None = None, sigma: ArrayLike | None = None, corr: ArrayLike | None = None, dt: float | None = None, model: MarketModel | None = None, reward: RewardFunction | None = None, sampler: str | int | EpisodeSampler = 'random', capital: float = 1000000.0)

Bases: Env[dict[str, NDArray[float32]], NDArray[float32]]

Allocate wealth across several assets and cash, rebalancing every step.

Prices are simulated (correlated geometric Brownian motions by default, or any model from sekai.finance.simulators, e.g. regime switching or stochastic volatility), or drawn as windows from a history: raw prices of shape (T, n_assets) (an array or a pandas DataFrame; one indexed by dates keeps them, as a MarketData would) or a MarketData with a complete panel as data. Pass one of prices, data and model. For universes whose members change over time, use Market-v0 instead.

  • Observation: Dict with "returns", a Box of shape (lookback, n_assets) holding recent log returns per asset, and "weights", a Box of shape (n_assets + 1,) holding the current weights after price drift, cash last.
  • Action, long-only (default): Box(0, 1, (n_assets + 1,)) of non-negative scores, normalised to target weights summing to 1 (cash last); all zeros means all cash.
  • Action, with allow_short=True: Box(-1, 1, (n_assets,)) of target asset weights, scaled down if gross exposure sum(|w|) exceeds 1; the rest is cash.
  • Reward: by default the log growth of portfolio value net of transaction costs, where cost = transaction_cost * turnover (or any CostModel) and turnover = sum(|target - drifted weights|). Pass reward= for another objective.
  • Truncation: after episode_length steps (an episode is a window of an ongoing market, so the observation holds no clock).
  • info: portfolio_value (also as equity), weights, turnover, cost, asset_returns, timestamp and dt (step length in years: from dated MarketData, else the model's dt, else 1/252, unless dt= is given).

With a history, reset(options={"start": t}) places the first decision at row t (an int) or timestamp t (a date, or sekai.finance.Time(t) for a numeric timestamp), and sampler chooses windows otherwise (see TradingEnv). n_assets (default 5 for the built-in simulation) follows the history or the model; given as well, it must agree with them. reward is copied, as is a reward_fn set later, so a stateful reward is never shared between envs. step() after the episode has ended raises ResetRequired.

reward_fn property writable

reward_fn: RewardFunction

The reward function. Setting it stores a copy, as reward= does, so a stateful reward set on several envs at once (e.g. with a vector env's set_attr) is not shared.

episode_length property writable

episode_length: int

Steps per episode (read-only: it sizes the price windows).

lookback property writable

lookback: int

Rows of returns in the observation (read-only: it sizes the observation).

elapsed_steps property

elapsed_steps: int

Steps taken in the current episode.

timestamp property

timestamp: Any

The time of the current decision, as in info["timestamp"] (the step when simulated).

price_history property

price_history: NDArray[float64]

The prices observed so far, lookback window included, ending at the current prices: shape (lookback + elapsed_steps + 1, n_assets).

action_to_weights

action_to_weights(action: Any) -> NDArray[float64]

Map an action to target weights over (assets..., cash).

MarketMakingEnv

MarketMakingEnv(*, s0: float = 100.0, sigma: float = 2.0, total_time: float = 1.0, n_steps: int = 200, fill_intensity: float = 140.0, fill_decay: float = 1.5, risk_aversion: float = 0.1, max_inventory: int = 50, order_size: int = 1, max_offset: float = 5.0)

Bases: Env[NDArray[float32], NDArray[float32]]

Quote a bid and an ask around a moving mid price and manage inventory risk.

The Avellaneda & Stoikov (2008) market-making model. The mid price follows arithmetic Brownian motion, S <- S + sigma * sqrt(dt) * Z. A quote at distance delta from the mid is filled within a step with probability 1 - exp(-A * exp(-k * delta) * dt), independently on each side; each fill is order_size units at the quoted price.

  • Observation: Box of shape (2,): inventory / max_inventory in [-1, 1] and the fraction of the session remaining in [0, 1]. This is the Markov state of the model (the price level does not affect optimal quotes).
  • Action: Box(0, max_offset, (2,)), the bid and ask distances from the mid. A side is not quoted while inventory is at its limit on that side.
  • Reward: change in mark-to-market wealth (cash + inventory * mid) minus a running inventory penalty 0.5 * risk_aversion * sigma**2 * dt * inventory**2.
  • Termination: at the end of the session (n_steps steps). The time remaining is observed, so this is a true terminal state, not a truncation.
  • info: mid_price, inventory, cash, wealth, bid, ask, bid_filled, ask_filled, timestamp (session time, in units of total_time) and dt. The last step's quotes and fills are also the attributes bid, ask, bid_filled and ask_filled (NaN and False after reset()).

avellaneda_stoikov_action() returns the model's closed-form optimal quotes, a strong baseline to compare learned policies against. step() after the session has ended raises ResetRequired.

Raises:

Type Description
ValueError

For a parameter out of range: total_time, fill_intensity and fill_decay must be positive, sigma, risk_aversion and max_offset non-negative, s0 finite, and n_steps, max_inventory, order_size >= 1.

wealth property

wealth: float

Mark-to-market wealth: cash plus inventory valued at the mid.

elapsed_steps property

elapsed_steps: int

Steps taken in the current episode.

fill_probability

fill_probability(offset: float) -> float

Probability that a quote offset away from the mid fills within one step.

avellaneda_stoikov_action

avellaneda_stoikov_action() -> NDArray[float32]

The model's closed-form optimal (bid, ask) distances in the current state.

OptimalExecutionEnv

OptimalExecutionEnv(*, initial_inventory: float = 1000000.0, s0: float = 50.0, sigma: float = 0.95, total_time: float = 5.0, n_steps: int = 25, permanent_impact: float = 2.5e-07, temporary_impact: float = 2.5e-06, fixed_cost: float = 0.0625, risk_aversion: float = 1e-06)

Bases: Env[NDArray[float32], NDArray[float32]]

Liquidate a large position over a fixed horizon, trading off impact against risk.

The Almgren & Chriss (2000) execution model, with defaults from the paper's example (1M shares of a $50 stock over 5 days). Each step the agent sells n shares, executed at S - epsilon - eta * n / tau (temporary impact); the mid then moves by sigma * sqrt(tau) * Z - gamma * n (volatility plus permanent impact).

  • Observation: Box of shape (3,): remaining inventory / initial inventory, fraction of the horizon remaining, and the mid's move since arrival in units of sigma * sqrt(total_time).
  • Action: Box(0, 1, (1,)), the fraction of the remaining inventory to sell this step. Whatever is left is sold at the final step regardless.
  • Reward, in basis points of the arrival notional X0 * S0: 1e4 * (n * (execution_price - S0) - penalty) / (X0 * S0), where penalty = risk_aversion * sigma**2 * tau * inventory_after**2. Summed over an episode this is minus (implementation shortfall + risk_aversion times the shortfall's variance proxy), the Almgren-Chriss mean-variance objective.
  • Termination: inventory fully sold, or the horizon reached.
  • info: shares_sold, execution_price, inventory, mid_price, implementation_shortfall (dollars, cumulative), shortfall_bps, timestamp (time since arrival, in units of total_time) and dt (the period tau).

almgren_chriss_actions() returns the closed-form optimal schedule as a sequence of actions; with risk_aversion=0 it is TWAP. step() after the episode has ended raises ResetRequired.

Raises:

Type Description
ValueError

For a parameter out of range: initial_inventory, s0, total_time and temporary_impact must be positive (the latter above permanent_impact * tau / 2), sigma, permanent_impact, fixed_cost and risk_aversion non-negative, and n_steps >= 1.

elapsed_steps property

elapsed_steps: int

Steps taken in the current episode.

almgren_chriss_trajectory

almgren_chriss_trajectory() -> NDArray[float64]

Optimal holdings x_0..x_N (x_0 = X0, x_N = 0) for this env's parameters.

almgren_chriss_actions

almgren_chriss_actions() -> NDArray[float32]

The optimal schedule as actions (fraction of remaining inventory per step).

expected_shortfall

expected_shortfall(trades: NDArray[Any]) -> tuple[float, float]

Closed-form mean and variance of the implementation shortfall for a static schedule.

MarketEnv

MarketEnv(data: MarketData | None = None, *, model: MarketModel | None = None, max_assets: int | None = None, features: Sequence[str] = (), exogenous: Sequence[str] = (), lookback: int = 20, episode_length: int = 252, sampler: str | int | EpisodeSampler = 'random', transaction_cost: float | CostModel = 0.001, reward: RewardFunction | None = None, allow_short: bool = False, max_leverage: float = 1.0, cash_rate: float = 0.0, margin_rate: float | None = None, borrow_rate: float = 0.0, capital: float = 1000000.0, dt: float | None = None, delisting_return: float = 0.0, privileged_state: bool = False)

Bases: Env[dict[str, Any], NDArray[float32]]

Multi-asset trading over a changing universe, with features, outside data and costs.

The general-purpose finance environment, assembled from sekai.finance components. Assets list and delist (NaN prices in a MarketData, or a ListingProcess market model), so the number of tradable assets changes from step to step; the observation is a fixed-size padded set (MaskedBox) with a validity mask, and the action mask marks the tradable slots.

Data. Either a historical MarketData as data (episodes are windows of it, chosen by sampler or reset(options={"start": ...})) or a model that simulates a fresh market each episode (default: default_market(), two hidden regimes and listings/delistings), not both.

Slots. With max_assets=None each asset has its own slot. With a smaller max_assets (a large historical universe, few names alive at once) assets are mapped to slots by a SlotMap: an asset keeps its slot while it is tradable, and newcomers take free slots (incumbents first, then in column order, if more are tradable than there are slots). env.slot_assets names the asset in each slot.

Observation (Dict):

  • "assets": MaskedBox of max_assets slots, each a (lookback, F) window of per-asset features: the log return, an availability flag (1 where the return is defined), then each data field named in features. Missing values are 0.
  • "portfolio": Box (max_assets + 1,), current weights per slot, cash last.
  • "account": Box (2,): log equity and gross exposure.
  • "market" (only with exogenous): Box (lookback, E) window of the market-wide series named in exogenous (MarketData exogenous series, or, in simulation, model state such as the regime, which makes it observable).

Action. Long-only (default): Box(0, 1, (max_assets + 1,)) scores, cash last, normalised into weights (scaled to max_leverage). Long/short: Box(-1, 1, (max_assets,)) target weights, scaled down if gross exposure exceeds max_leverage. Untradable slots are ignored.

Dynamics. At each decision the account rebalances to the target weights, pays transaction_cost (any CostModel), then holds them over the next period: assets earn their returns, cash earns cash_rate (or pays margin_rate when borrowed), shorts pay borrow_rate. An asset that delists during the period returns delisting_return and its position becomes cash.

Reward: reward(outcome) for a sekai.finance.RewardFunction, by default the log growth of equity. Termination: equity wiped out. Truncation: after episode_length steps. An episode is a window of an ongoing market, so its end is a truncation and the observation holds no clock; wrap the env in TimeAwareObservation(env, max_episode_steps=episode_length) to observe one.

info: equity, turnover, cost, financing, gross_exposure, n_assets (tradable), weights (per slot), timestamp (the data's timestamp, or the step index in simulation), dt; plus state (the simulation's latent state at the new decision time) when privileged_state=True, for asymmetric actor-critic methods.

step() after the episode has ended raises ResetRequired.

Parameters:

Name Type Description Default
data MarketData | None

Historical data (NaN prices = not listed).

None
model MarketModel | None

Market model for simulation when no data is given.

None
max_assets int | None

Slots in the observation (default: every asset).

None
features Sequence[str]

Names of per-asset data fields to observe.

()
exogenous Sequence[str]

Names of market-wide series to observe.

()
lookback int

Rows in each observation window.

20
episode_length int

Decisions per episode.

252
sampler str | int | EpisodeSampler

Placement of episodes in the history ("random", "sequential", an int, or a sekai.finance.EpisodeSampler).

'random'
transaction_cost float | CostModel

Proportional rate or CostModel, applied per asset (so per-asset parameters follow the assets, whatever their slots). Impact and spread models read the volume (shares) and spread (fraction of the price) data fields when present.

0.001
reward RewardFunction | None

Reward function (default LogReturn()). The env keeps a copy (as it does of a reward_fn set later), so a stateful reward is never shared.

None
allow_short bool

Long/short action space.

False
max_leverage float

Cap on gross exposure.

1.0
cash_rate float

Annual interest earned on positive cash.

0.0
margin_rate float | None

Annual interest paid on borrowed cash (default: cash_rate).

None
borrow_rate float

Annual fee on the value of short positions.

0.0
capital float

Account size in currency (for fixed and impact costs).

1000000.0
dt float | None

Years per step (default: from the data's timestamps, the model, or 1/252).

None
delisting_return float

Return of an asset over the period in which it delists.

0.0
privileged_state bool

Add the simulation's latent state to info.

False

reward_fn property writable

reward_fn: RewardFunction

The reward function. Setting it stores a copy, as reward= does, so a stateful reward set on several envs at once (e.g. with a vector env's set_attr) is not shared.

slot_assets property

slot_assets: tuple[Any, ...]

The asset in each slot (MarketData asset ids, or column indices), None if empty.

episode_length property writable

episode_length: int

Decisions per episode (read-only: it sizes the episode's data window).

lookback property writable

lookback: int

Rows in each observation window (read-only: it sizes the observation).

timestamp property

timestamp: Any

The time of the current decision, as in info["timestamp"] (the step when simulated).

latent_state property

latent_state: dict[str, NDArray[float64]]

The simulation's latent state at the current decision (e.g. {"regime": ...}), as info["state"] carries it with privileged_state=True; empty for historical data.

action_mask

action_mask() -> NDArray[int8]

1 for slots that can be traded now (and for cash, in the long-only action).

target_weights

target_weights(action: Any) -> NDArray[float64]

The per-slot weights an action asks for in the current state.

default_market

default_market(n_assets: int = 10) -> MarketModel

The simulated market of Market-v0: a two-regime market whose assets list and delist.

A calm regime (8% drift, 15% volatility, mild correlation) and a stressed one (-20% drift, 40% volatility, high correlation) alternate as a hidden Markov chain with persistent regimes (about 2 years calm, 6 months stressed on average). Each asset lists and delists over time, so the universe changes within an episode.

LimitOrderBookEnv

LimitOrderBookEnv(*, flow: ZeroIntelligenceFlow | None = None, mid_price: int = 10000, session_length: float = 300.0, decision_interval: float = 1.0, levels: int = 5, max_quote_offset: int = 5, order_size: int = 1, max_inventory: int = 20, inventory_penalty: float = 0.01, max_trades: int = 16, warmup: float = 20.0)

Bases: Env[dict[str, Any], NDArray[int64]]

Make markets or trade in a simulated limit order book, against background order flow.

Every decision_interval units of time the agent chooses where to rest a bid and an ask (or not to quote), and whether to send a market order. In between, background traders submit limit orders, market orders and cancellations as Poisson events (sekai.finance.ZeroIntelligenceFlow), which may fill the agent's resting quotes. A quote left at the same price keeps its place in the queue; moving it sends it to the back.

A decision runs in this order: the market order, if any, after cancelling the agent's own quote on the opposite side (self-trade prevention: a market buy cancels the agent's resting ask first, a market sell its bid, so the agent never trades with itself); then the bid and the ask are updated per the action (a quote cancelled for the market order is placed again, at the back of its queue, unless its level is 0).

  • Action: MultiDiscrete([K + 1, K + 1, 3]) with K = max_quote_offset: the bid level (0 = no bid, 1 = join the best bid, k = k - 1 ticks behind it), the ask level (likewise), and a market order (0 sell, 1 none, 2 buy) of order_size lots. Levels are measured from the best price of the other traders' orders (one tick inside the opposite best if there are none). A resting quote already at the requested level is left alone, with its queue position, and a level-1 quote stays put while it is the best price itself, even alone there; so an unchanged action moves a quote only to follow the other traders' best price, and never pulls a level-1 quote off the best. action_mask() disables buying at the long inventory limit and selling at the short one.
  • Observation (Dict): "book", Box (levels, 4): bid price offset from the mid (ticks), bid size, ask offset, ask size for the best levels levels; "trades": MaskedBox of the last max_trades trades (price offset from the current mid, size, aggressor side +1 buy / -1 sell), a variable-size set; "agent": inventory / max_inventory, fraction of the session remaining, whether a bid and an ask are resting, and the volume queued ahead of each.
  • Reward: change in mark-to-market wealth (cash + inventory * mid, in ticks) minus the running penalty inventory_penalty * inventory^2 * dt for the time dt the step covers.
  • Termination: at the end of the session (the clock is observed); step() after it raises ResetRequired.
  • info: timestamp (session time), dt (the time the step covered: decision_interval, or less on a final, shorter step), mid_price, spread, inventory, cash, wealth, fills (agent fills this step).

Parameters:

Name Type Description Default
flow ZeroIntelligenceFlow | None

Background order flow (default ZeroIntelligenceFlow()).

None
mid_price int

Initial mid price in ticks.

10000
session_length float

Length of the trading session (time units of the flow rates).

300.0
decision_interval float

Time between decisions.

1.0
levels int

Book levels observed.

5
max_quote_offset int

Farthest quote level K.

5
order_size int

Lots per quote or market order.

1
max_inventory int

Absolute inventory limit.

20
inventory_penalty float

Running penalty per squared lot of inventory per unit of time.

0.01
max_trades int

Recent trades kept in the observation.

16
warmup float

Time the book evolves before the session starts.

20.0

book property

book: OrderBook

The live order book (read-only use, please).

action_mask

action_mask() -> tuple[NDArray[int8], ...]

Per-dimension masks: no buying at the long limit, no selling at the short one.

LimitOrderBookMarket

LimitOrderBookMarket(n_agents: int = 2, *, decision_intervals: float | Sequence[float] = 1.0, jitter: float = 0.0, flow: ZeroIntelligenceFlow | None = None, mid_price: int = 10000, session_length: float = 300.0, levels: int = 5, max_quote_offset: int = 5, order_size: int = 1, max_inventory: int = 20, inventory_penalty: float = 0.01, max_trades: int = 16, warmup: float = 20.0)

Bases: MultiAgentEnv[dict[str, Any], NDArray[int64], str]

Several RL traders in one limit order book, each acting on its own clock.

An event-driven multi-agent market: trader i decides every decision_intervals[i] units of time (optionally jittered), so at any moment only some traders act. Each step() takes actions from exactly the traders whose turn it is, advances the book (background flow and every trader's resting orders) to the next decision time of any trader, and returns:

  • observations only for the traders that act next (the event-driven convention: the keys tell you who moves);
  • rewards, terminated, truncated and info for every trader, so rewards earned between a trader's decisions are not lost (sum them until its next observation).

Traders' orders interact: one trader's resting quote can be filled by another's market order (but never by its own: self-trade prevention, as in LimitOrderBookEnv). The inventory penalty accrues per unit of time, so a trader's penalty does not depend on how often the others act; info[agent]["dt"] is the time the step covered. Traders whose decision times coincide act in name order. Every action is validated before any is carried out, so a step that raises InvalidAction changes nothing. Actions, observations, masks (action_mask(agent)) and rewards per trader are as in LimitOrderBookEnv. At the end of the session every trader is terminated (and agents is empty); step() then raises ResetRequired.

Parameters:

Name Type Description Default
n_agents int

Number of traders, named "trader_0", "trader_1", ...

2
decision_intervals float | Sequence[float]

Each trader's time between decisions (one value for all, or one per trader).

1.0
jitter float

Each wait is multiplied by a random factor in [1 - jitter, 1 + jitter].

0.0
flow ZeroIntelligenceFlow | None

Background order flow (default ZeroIntelligenceFlow()).

None
mid_price int

Initial mid price in ticks.

10000
session_length float

Length of the trading session.

300.0
levels int

Book levels observed.

5
max_quote_offset int

Farthest quote level K.

5
order_size int

Lots per quote or market order.

1
max_inventory int

Absolute inventory limit per trader.

20
inventory_penalty float

Running penalty per squared lot of inventory per unit of time.

0.01
max_trades int

Recent trades kept in each observation.

16
warmup float

Time the book evolves before the session starts.

20.0

Finance renderers

Exported from sekai.envs.finance. Each is a sekai.rendering.Dashboard; render(env) returns an (H, W, 3) uint8 frame of the episode so far.

TradingRenderer

TradingRenderer(*, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws Trading-v0 (TradingEnv).

The price, with the lookback window before the episode in a muted tone; the position shaded behind it (long in the up colour, short in down, deeper for larger positions); trades as triangles at the price where they were decided; and the equity curve against buying and holding the asset.

PortfolioRenderer

PortfolioRenderer(*, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws Portfolio-v0 (PortfolioEnv).

The portfolio's value and every asset's price indexed to 100 at the start of the episode (on one axis, so they compare directly), the allocation over time, and the current weights. Each asset keeps its colour in every chart; cash is grey.

MarketRenderer

MarketRenderer(*, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws Market-v0 (MarketEnv).

Equity, with the periods the simulated market spends outside its first regime shaded (in the default market, the stressed one); the universe over time (one row per slot: filled while held, grey while tradable, empty while no asset is listed there); and the current holdings by slot.

MarketMakingRenderer

MarketMakingRenderer(*, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws MarketMaking-v0 (MarketMakingEnv).

The mid price with the agent's bid and ask quotes around it and its fills (triangles at the price of each fill: up for a filled bid, down for a filled ask), its inventory, and its mark-to-market profit and loss.

OptimalExecutionRenderer

OptimalExecutionRenderer(*, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws OptimalExecution-v0 (OptimalExecutionEnv).

The shares still to sell against the Almgren-Chriss optimal schedule (dashed, over the whole horizon), the shares sold each step against the schedule's, and the mid price with the average price of each step's execution and the arrival price.

LimitOrderBookRenderer

LimitOrderBookRenderer(*, levels: int = 8, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: _EnvRenderer

Draws LimitOrderBook-v0 (LimitOrderBookEnv).

The mid price with the market's spread and the agent's fills; its inventory and wealth; and the order book as a depth ladder in which the agent's resting orders are highlighted at their place in the queue.

LimitOrderBookMarketRenderer

LimitOrderBookMarketRenderer(*, levels: int = 8, width: int = 720, height: int = 450, theme: Theme | str = 'light', window: int | None = None)

Bases: Dashboard

Draws LimitOrderBookMarket, several traders in one order book.

The mid price and spread over the session, each trader's inventory and wealth (one colour per trader, in every chart), and the order book with each trader's resting orders highlighted in its colour. The time axis is session time, since traders act on their own clocks. Render it directly (renderer.render(env) after each step): RecordFrames wraps single-agent environments only.

Native vector environments

Registered as the vector_entry_point of their environments, so sekai.make_vec(id, n) returns them; see Native vector environments.

TradingVecEnv

TradingVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)

Bases: _TemplateVecEnv[NDArray[float32], Any]

Trading-v0 for num_envs sub-envs at once (see TradingEnv for the parameters).

max_episode_steps truncates every sub-env after that many steps, as the TimeLimit that make() adds would.

price property

price: Array

The current asset price of every sub-env.

timestamp property

timestamp: NDArray[Any]

The time of every sub-env's current decision, as in infos[i]["timestamp"].

price_history property

price_history: NDArray[object_]

Every sub-env's prices observed so far, lookback included (see TradingEnv.price_history): an object array with one price array per sub-env.

PortfolioVecEnv

PortfolioVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)

Bases: _TemplateVecEnv[dict[str, NDArray[float32]], NDArray[float32]]

Portfolio-v0 for num_envs sub-envs at once (see PortfolioEnv for the parameters).

max_episode_steps truncates every sub-env after that many steps, as the TimeLimit that make() adds would.

timestamp property

timestamp: NDArray[Any]

The time of every sub-env's current decision, as in infos[i]["timestamp"].

price_history property

price_history: NDArray[object_]

Every sub-env's prices observed so far, lookback included (see PortfolioEnv.price_history): an object array with one (k, n_assets) array per sub-env.

action_to_weights

action_to_weights(actions: Any) -> Array

Batched PortfolioEnv.action_to_weights: (num_envs, n_assets + 1) targets.

MarketMakingVecEnv

MarketMakingVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)

Bases: _TemplateVecEnv[NDArray[float32], NDArray[float32]]

MarketMaking-v0 for num_envs sub-envs at once (see MarketMakingEnv).

max_episode_steps truncates every sub-env after that many steps, as the TimeLimit that make() adds would.

wealth property

wealth: Array

Mark-to-market wealth of every sub-env.

time_remaining property

time_remaining: Array

Session time left in every sub-env.

avellaneda_stoikov_action

avellaneda_stoikov_action() -> NDArray[float32]

Closed-form optimal (bid, ask) distances for every sub-env, shape (num_envs, 2).

OptimalExecutionVecEnv

OptimalExecutionVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)

Bases: _TemplateVecEnv[NDArray[float32], NDArray[float32]]

OptimalExecution-v0 for num_envs sub-envs at once (see OptimalExecutionEnv).

max_episode_steps truncates every sub-env after that many steps, as the TimeLimit that make() adds would.

almgren_chriss_actions

almgren_chriss_actions() -> NDArray[float32]

The optimal static schedule (identical for every sub-env), shape (n_steps, 1).

Price simulation

simulate_gbm

simulate_gbm(rng: Generator, n_steps: int, *, s0: ArrayLike = 100.0, mu: ArrayLike = 0.0, sigma: ArrayLike = 0.2, dt: float = 1 / 252, corr: ArrayLike | None = None) -> NDArray[float64]

Simulate geometric Brownian motion price paths.

Uses the exact log-normal transition, so prices stay strictly positive.

Parameters:

Name Type Description Default
rng Generator

Source of randomness.

required
n_steps int

Number of steps; the result has n_steps + 1 rows (row 0 is s0).

required
s0 ArrayLike

Initial price. Scalars for s0, mu and sigma give a single path of shape (n_steps + 1,); length-n arrays give n assets and shape (n_steps + 1, n).

100.0
mu ArrayLike

Annualised drift (scalar or length-n array).

0.0
sigma ArrayLike

Annualised volatility (scalar or length-n array).

0.2
dt float

Step length in years (1/252 is one trading day).

1 / 252
corr ArrayLike | None

Optional (n, n) correlation matrix between the assets' shocks.

None

random_correlation

random_correlation(rng: Generator, n: int, strength: float = 0.5) -> NDArray[float64]

A random valid correlation matrix with typical off-diagonal size ~strength.

sample_window

sample_window(rng: Generator, prices: NDArray[Any], length: int) -> NDArray[float64]

A random contiguous window of length rows from historical prices.