News Vendor Problem
Imagine a newspaper vendor who buys copies wholesale every morning and sells them through the day. Demand is uncertain, as some days are busy and some quiet. If the vendor buys too few copies, they miss out on sales (and on the profit those sales would have brought in). If they buy too many, the leftover papers are wasted at the end of the day, since yesterday's news doesn't sell tomorrow. How many copies should the vendor buy? That is the newsvendor problem, finding the order quantity that, on average, gives the highest profit when you weigh the cost of running out against the cost of leftovers. The same balance shows up for any perishable product with unpredictable demand, such as fresh produce, baked goods, daily prepared meals, magazines and flowers.
Problem Definition
The newsvendor orders Q units every day. Each unit costs c, and the sell price is p. The newsvendor faces normally distributed demand with mean μ and standard deviation σ. If, on a certain day, the actual demand is k, then the net profit after selling k units is
Profit = Revenue − Order Cost = k × p − Q × c
However, any units left unsold at the end of the day are wasted, since they cannot be sold the next day. The newsvendor can sell these unsold units back to the supplier at a lower price, called the salvage value s. If the actual demand that day is less than the order size Q, then the net profit is
Profit = Revenue + Salvage − Order Cost = k × p + (Q − k) × s − Q × c
When the actual demand k is more than the newsvendor's order quantity Q, the profit is
Profit = Q × p − Q × c (In this case, all available units are sold, and the profit is the revenue from selling Q units minus the total ordering cost.)
So, the newsvendor problem is defined by the following parameters.
- Ordering cost (c). Cost per unit ordered.
- Sell price (p). Price per unit sold.
- Salvage value (s). Value per unsold unit at the end of the period.
- Mean demand (μ). Average demand during the period.
- Standard deviation of demand (σ). How much the demand varies.
- Number of samples (n). Number of demand samples for the simulation.
- Order quantity (Q). Units ordered for the period.
The objective is to find the order quantity Q* that maximizes the expected profit.
Analytical Solution
The optimal order quantity is
Q* = μ + σ · Φ−1 (Cu / (Cu + Co))
where
- Cu = p − c is the underage cost (profit lost per unit of demand that could not be met),
- Co = c − s is the overage cost (cost per unsold unit),
- Φ−1 is the inverse of the standard normal distribution, and
- Cu/(Cu + Co) is the critical ratio (the share of demand to satisfy).
Example Parameters
The parameters in this example are
c = 2
p = 5
s = 1
μ = 100
σ = 15
n = 1000
Q = 100 (example order quantity)
Putting in the values
Cu = 5 − 2 = 3
Co = 2 − 1 = 1
Critical ratio = 3/(3 + 1) = 0.75
Q = 100 + 15 · Φ−1(0.75)
Q ≈ 100 + 15 · 0.6745 = 110.12
So, the optimal order quantity is about 110 units.
Modeling and Simulating the Newsvendor Problem
It is a simple supply chain with three nodes, an infinite supplier, the newsvendor (a retailer), and a customer demand at the newsvendor with normally distributed order sizes. One time unit is read as one day.

Simulation Setup
- An infinite_supplier gives the newsvendor an unlimited supply.
- The simulation focuses on the newsvendor,
newsvendor1, which- keeps a perishable inventory with a shelf life of 1 day, and
- uses a periodic replenishment policy, placing a replenishment order every day.
- The link between the supplier and the newsvendor has a lead time of 0, so each day's order arrives at once.
- The customer demand places one customer order a day, with an order size drawn from a normal distribution by the
normal_quantity()function. - With
consume_availableset to True, a customer whose order cannot be filled in full takes the units that are there. This is needed because the simulation represents the whole day's demand as a single customer order, while in reality many customers each buy a newspaper. - After the run, the profit is worked out from the units sold, the expired units (sold back at the salvage value) and the ordering cost.
import simpy
import random
import numpy as np
import matplotlib.pyplot as plt
import SupplyNetPy.Components as scm
random.seed(42) # fixed seed for repeatable results
mean = 100
std_dev = 15
num_samples = 1000
def normal_quantity():
"""Generates a random order quantity based on a normal distribution."""
sample = random.gauss(mean, std_dev)
while(sample<0):
sample = random.gauss(mean, std_dev)
return int(sample)
cost = 2 # newspaper buy price
sell_price = 5 # newspaper sell price
salvage = 1 # newspaper salvage price
order_quantity = 100 # initial order quantity
cost_arr = [] # to store cost for each order quantity
profit_arr = [] # to store profit for each order quantity
for order_quantity in range(10, 200, 10):
env = simpy.Environment()
# create an infinite supplier
supplier1 = scm.Supplier(env=env, ID="S1", name="Supplier1", node_type="infinite_supplier")
#create the distributor
newsvendor1 = scm.InventoryNode(env=env, ID="D1", name="News Vendor", node_type="distributor",
capacity=float('inf'), initial_level=order_quantity,
inventory_holding_cost=0.1, inventory_type="perishable",
shelf_life=1, replenishment_policy=scm.PeriodicReplenishment,
policy_param={"T": 1, "Q": order_quantity},
product_sell_price=sell_price, product_buy_price=cost)
link1 = scm.Link(env=env,ID="l1", source=supplier1, sink=newsvendor1, cost=10, lead_time=lambda: 0)
demand1 = scm.Demand(env=env,ID="d1", name="demand_d1",
order_arrival_model=lambda: 1, consume_available=True,
order_quantity_model=normal_quantity, demand_node=newsvendor1)
scm.global_logger.disable_logging()
env.run(until=num_samples)
# Calculate the cost and profit
# total demand fulfilled by newsvendor is available in variable demand_fulfilled
# list element 0 is number of orders, element 1 is total quantity
daily_sales = newsvendor1.stats.demand_fulfilled[1]
wasted_inventory = newsvendor1.inventory.waste
everyday_profit = (daily_sales*sell_price + wasted_inventory*salvage - order_quantity*num_samples*cost)/num_samples
profit_arr.append([order_quantity,everyday_profit])
profit_arr = np.array(profit_arr) # convert to numpy array for easier manipulation
Q = np.argmax(profit_arr[:,1]) # index of maximum profit
plt.plot(profit_arr[:,0], profit_arr[:,1], marker='.', linestyle='-', color='b')
plt.plot(profit_arr[Q,0], profit_arr[Q,1], marker='o', linestyle='-', color='r', label=f'Q={profit_arr[Q,0]}')
plt.xlabel('Order Quantity')
plt.ylabel('Profit')
plt.title('Profit vs Order Quantity')
plt.legend()
plt.grid()
plt.show()
The following plot shows how the profit changes with the order quantity (Q). The highest profit is reached when Q is about 110, which confirms the analytical solution for the optimal order quantity.

Takeaway
This example showed how to solve the newsvendor problem by simulation with the SupplyNetPy library. By simulating different order quantities and comparing the resulting profits, we can see and confirm the order quantity that maximizes the expected profit. This approach can be extended to more complex inventory scenarios and demand distributions, giving useful insights for inventory decisions.