Skip to content

Guide

The Guide explains each part of Stockcast in depth: what it does, the ideas behind it, and every option it takes. New to Stockcast? The Walkthrough introduces the same parts in nine short steps.

I want to…

Find the building block for your job, the page that explains it, and a notebook that uses it.

I want to… Use Read See it in
start from my current stock and open orders InventoryStateDataFrame, with_open_orders Inventory state 01, 05d
feed in sales history or simulated demand demand table, DemandGenerator Demand and calendars 02, 02c
forecast from sales that ran out of stock demand vs fulfilled_units in the event table, get_history() in predict Sales and demand, When the forecast learns from the run 04a
turn my forecast into a stock target target table + fit Forecast targets, Connect any forecasting model 04c, 04e
order up to a level at every review OrderUpToPolicy Order-up-to 04, 06
order only when stock falls low ReorderPointPolicy Reorder point 05b, 05g, 08
buy once for a season SingleOrderPolicy, newsvendor_critical_fractile Single order 04b
use my own ordering rule subclass BasePolicy Write your own policy 05
order on specific days only PeriodicSchedule, ExplicitSchedule, DecisionSchedule Decision schedules 02b, 04f
simulate a policy over time SimulationEngine.run The simulation engine 02
use a lead time from a paper or another tool lead_time, PeriodicSchedule(start=...) Work with any timing convention –
read what happened in a run to_event_frame, validate_event_frame The event table 02, 08
measure service, stock, and cost InventoryEvaluator, metrics Evaluation and metrics, Put costs on a run 07, 09
plot a run or a comparison plot_simulation_dashboard, plot_comparison_dashboard Plots 02, 06
respect minimums, case sizes, or capacity OrderingConstraints and friends Ordering constraints 05c, 09
add holidays, promotions, or stock corrections SimulationCallback, scheduled callbacks Callbacks 08, 05c
use several suppliers or random lead times SupplyModel, Supplier, SupplierAllocation Suppliers and open orders 05d
model late or short deliveries DeliveryOutcome Unreliable deliveries 05f
model products that expire, inspections, returns ShelfLife, InventoryProcess Shelf life and processes 05e, 07
refresh targets as new forecasts arrive policy_schedule Refresh targets as forecasts roll 04a, 04d, 09
compare forecasts or policies fairly run_comparison Compare forecasts and policies 06, 09
compute real orders every period advance_period → predict → constraints → fulfill_demand Use Stockcast in production, Walkthrough step 9 10

All together

Every optional part is one argument of run. This run adds two ordering constraints, a holiday callback, a supplier whose deliveries sometimes take a day longer, and shelf life to the tea shop. Remove any of them and the rest still works.

Setup: the tea shop from the Walkthrough
import numpy as np
import pandas as pd

from stockcast.core import InventoryStateDataFrame, SimulationEngine
from stockcast.policies import OrderUpToPolicy
from stockcast.utils import DemandGenerator

sku = "tea_250g"
opening_date = pd.Timestamp("2026-01-05")
lead_time, review_period = 2, 4
horizon = lead_time + review_period

demand = DemandGenerator(
    [sku], first_date=opening_date + pd.Timedelta(days=1), freq="D",
    random_seed=3, negative_demand_handling="clip_zero",
).seasonal(n_periods=56, base=6.0, amplitude=2.0, season_length=7, std=2.0)
demand["y"] = demand["y"].round()

inventory = InventoryStateDataFrame.from_observed(
    pd.DataFrame({"unique_id": [sku], "date": [opening_date], "on_hand": [30.0]}),
    allow_backorders=False,   # some pages step this state by hand
)

paths = np.random.default_rng(42).poisson(6.0, size=(10_000, horizon))


def tea_policy(probability):
    """Order-up-to policy whose target is the given quantile of 6-day demand."""
    target = pd.DataFrame({
        "unique_id": [sku],
        "target": [np.quantile(paths.sum(axis=1), probability)],
        "date": [opening_date + pd.Timedelta(days=horizon)],   # the last day it covers
    })
    return OrderUpToPolicy(
        lead_time=lead_time, review_period=review_period, freq="D",
        service_level=probability, allow_backorders=False,
    ).fit(target, target_column="target")


policy = tea_policy(0.95)
run_settings = dict(random_seed=3)
result = SimulationEngine().run(
    policy=policy, demand_source=demand, inventory=inventory, **run_settings,
)
from stockcast.core import (
    InventoryStateDataFrame, MinimumOrderQuantity,
    OrderMultiple, ScheduledOrderHold, ShelfLife, SimulationEngine, Supplier,
    SupplyModel,
)

# Orders of at least 12 packs, in cases of 6.
constraints = [
    MinimumOrderQuantity(12, mode="adjust"),
    OrderMultiple(6, mode="adjust"),
]

# The supplier is closed on 18 January: skip that review.
holiday = ScheduledOrderHold(pd.DataFrame({
    "unique_id": [sku],
    "date": [pd.Timestamp("2026-01-18")],
    "reason": ["supplier closed"],
    "source": ["supplier calendar"],
}))

# Deliveries take 2 days, sometimes 3.
supply = SupplyModel(
    [Supplier("tea_wholesaler", lead_time={2: 0.8, 3: 0.2})],
    random_seed=11,
)

# Tea keeps for 21 days; the opening 30 packs arrived three days ago.
shelf_life = ShelfLife(
    shelf_life_days=21,
    opening_lots=pd.DataFrame({
        "unique_id": [sku],
        "received_date": [opening_date - pd.Timedelta(days=3)],
        "quantity": [30.0],
    }),
)

inventory_3 = InventoryStateDataFrame.from_observed(
    pd.DataFrame({"unique_id": [sku], "on_hand": [30.0]}), opening_date=opening_date,
)

result = SimulationEngine().run(
    policy=policy,
    demand_source=demand,
    inventory=inventory_3,
    order_constraints=constraints,
    callbacks=[holiday],
    supply=supply,
    processes=[shelf_life],
    **run_settings,
)

events = result.to_event_frame()
events.loc[events["requested_order_quantity"] > 0,
           ["date", "requested_order_quantity", "callback_adjusted_order_quantity",
            "order_quantity", "binding_constraints"]].head(6)
         date  requested_order_quantity  callback_adjusted_order_quantity  order_quantity     binding_constraints
0  2026-01-06                      16.0                              16.0            18.0          order_multiple
4  2026-01-10                      25.0                              25.0            30.0          order_multiple
8  2026-01-14                       9.0                               9.0            12.0  minimum_order_quantity
12 2026-01-18                      29.0                               0.0             0.0
16 2026-01-22                      46.0                              46.0            48.0          order_multiple
20 2026-01-26                       9.0                               9.0            12.0  minimum_order_quantity

Read across a row and you see each component at work: the policy asked for 16 packs, the case-pack rule rounded it to 18; on 18 January the holiday callback set the order to zero. The order frame (result.to_order_frame()) shows which deliveries took three days, and the callback audit (result.to_callback_audit_frame()) records the holiday with its reason.

Lead-time warning

This run prints a UserWarning: the policy's target was sized for a 2-day lead time, and some deliveries take 3. Stockcast simulates what you declared and warns that the two differ; that mismatch is often what you want to study. See Suppliers: the lead-time assumption.

See also: Notebook 05c: extension points · Notebook 09: a full experiment · Philosophy: why Stockcast is built this way