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.
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.
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
¶
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.
MountainCarRenderer
¶
Bases: BaseRenderer
Draws MountainCar-v0 and MountainCarContinuous-v0.
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:
Boxof shape(lookback + 1,): the lastlookbacklog returns followed by the current position (drifted with the price, see below). - Action:
Discrete(len(positions)), an index intopositions, 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
pto the targetwcostscost(by defaulttransaction_cost * |w - p|), then the position earns the asset's returnrand 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. Passreward=(asekai.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_lengthsteps (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) anddt(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 |
None
|
data
|
MarketData | None
|
A one-asset |
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 |
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 |
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 |
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).
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:
Dictwith"returns", aBoxof shape(lookback, n_assets)holding recent log returns per asset, and"weights", aBoxof 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 exposuresum(|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 anyCostModel) andturnover = sum(|target - drifted weights|). Passreward=for another objective. - Truncation: after
episode_lengthsteps (an episode is a window of an ongoing market, so the observation holds no clock). - info:
portfolio_value(also asequity),weights,turnover,cost,asset_returns,timestampanddt(step length in years: from dated MarketData, else themodel's dt, else 1/252, unlessdt=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).
timestamp
property
¶
timestamp: Any
The time of the current decision, as in info["timestamp"] (the step when simulated).
price_history
property
¶
The prices observed so far, lookback window included, ending at the current prices: shape (lookback + elapsed_steps + 1, n_assets).
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:
Boxof shape(2,): inventory /max_inventoryin [-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 penalty0.5 * risk_aversion * sigma**2 * dt * inventory**2. - Termination: at the end of the session (
n_stepssteps). 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 oftotal_time) anddt. The last step's quotes and fills are also the attributesbid,ask,bid_filledandask_filled(NaN and False afterreset()).
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: |
fill_probability
¶
Probability that a quote offset away from the mid fills within one step.
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:
Boxof shape(3,): remaining inventory / initial inventory, fraction of the horizon remaining, and the mid's move since arrival in units ofsigma * 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), wherepenalty = risk_aversion * sigma**2 * tau * inventory_after**2. Summed over an episode this is minus (implementation shortfall +risk_aversiontimes 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 oftotal_time) anddt(the periodtau).
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: |
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":MaskedBoxofmax_assetsslots, 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 infeatures. 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 withexogenous):Box (lookback, E)window of the market-wide series named inexogenous(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 |
'random'
|
transaction_cost
|
float | CostModel
|
Proportional rate or |
0.001
|
reward
|
RewardFunction | None
|
Reward function (default |
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
¶
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
¶
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
¶
1 for slots that can be traded now (and for cash, in the long-only action).
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])withK = 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) oforder_sizelots. 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 bestlevelslevels;"trades":MaskedBoxof the lastmax_tradestrades (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 * dtfor the timedtthe step covers. - Termination: at the end of the session (the clock is observed);
step()after it raisesResetRequired. - 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 |
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
|
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:
observationsonly for the traders that act next (the event-driven convention: the keys tell you who moves);rewards,terminated,truncatedandinfofor 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 |
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.
timestamp
property
¶
The time of every sub-env's current decision, as in infos[i]["timestamp"].
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.
MarketMakingVecEnv
¶
MarketMakingVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)
OptimalExecutionVecEnv
¶
OptimalExecutionVecEnv(num_envs: int, *, autoreset: bool = True, max_episode_steps: int | None = None, **kwargs: Any)
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
¶
A random valid correlation matrix with typical off-diagonal size ~strength.