SilentCoreTech

Shopify inventory forecasting: stockouts, dead stock and expiry

Topic: E-commerce 10 min read Updated

Most Shopify merchants track inventory as a single number: units on hand. That number tells you what you have. It does not tell you what it is costing you — and inventory costs money in three directions at once.

The three ways inventory loses money

Dead stock

Units that stopped selling still consume cash, warehouse space and, if you are paying for 3PL storage, a monthly fee. The cost is invisible because it never appears as a line item — it appears as cash you do not have.

Stockouts

A stockout on a bestseller costs more than the missed margin. Ads keep running against an unavailable product, the listing loses ranking, and a share of those customers buy from someone else permanently. The true cost is roughly missed margin + wasted ad spend + a slice of lifetime value.

Expiry

For cosmetics, supplements, food and anything with a batch date, value does not decay gradually — it goes to zero on a specific day. This is the leak most forecasting tools ignore entirely, because they model demand without modelling the shelf life of what is already on the shelf.

These three are usually reviewed in three different places: a sales report, a low-stock alert and a spreadsheet of batch dates. They are the same decision. Reviewing them separately is what produces the classic mistake — reordering a product that is about to expire.

What your Shopify data already tells you

Before adding any tool, four fields per SKU get you most of the way:

  • Sales velocity — units sold per day, over a trailing window long enough to be stable (28 days is a reasonable default; 7 is noise for most catalogues).
  • On-hand quantity — per location, not aggregated, if you ship from more than one.
  • Supplier lead time — the real one, measured from your last few purchase orders, not the one on the supplier's website.
  • Batch or lot expiry — if applicable. Shopify does not track this natively, which is why it usually lives in a spreadsheet.

Building a usable demand forecast

You do not need machine learning to beat a low-stock alert. A trailing average with a seasonality adjustment outperforms gut feel on almost every catalogue:

daily_velocity = units_sold_last_28d / 28
days_of_cover  = on_hand / daily_velocity
stockout_date  = today + days_of_cover

Two refinements matter more than model sophistication:

  • Exclude stockout days from the velocity window. If a product was unavailable for nine of the last 28 days, dividing by 28 understates real demand by about a third — and you will under-order again.
  • Weight recent weeks higher. A simple weighted average (for example 3× the last 7 days, 2× the prior 7, 1× the rest) reacts to trend without overreacting to a single good day.

Reorder points and lead time

A reorder point is the stock level at which you must place an order to avoid running out before it arrives. It has two components — expected demand during the lead time, and a buffer for the variance in both:

reorder_point = (daily_velocity × lead_time_days) + safety_stock
safety_stock  = daily_velocity × lead_time_days × service_factor

A service factor around 0.3–0.5 covers most consumer-goods variability. Raise it for products with volatile demand or unreliable suppliers; lower it for slow, predictable movers where holding cost outweighs stockout risk.

SignalThresholdAction
Days of cover< lead time + bufferReorder now
Days of cover> 180 daysReview as dead stock
Sell-through before expiry< 100%Discount, do not reorder
Velocity change (28d vs prior)> ±40%Re-forecast before ordering

When not to reorder

This is the decision every conventional forecasting tool gets wrong. When stock runs low, they say reorder. But if a large share of the current batch will expire before it sells, reordering adds cash to a position that is already going to be written off.

The check is straightforward:

projected_sales_before_expiry = daily_velocity × days_until_expiry
units_expiring_unsold = max(0, on_hand − projected_sales_before_expiry)

If units_expiring_unsold is material, the profitable move is the opposite of reordering: clear the batch. A time-boxed discount that moves the stock before the expiry date recovers a meaningful share of a value that would otherwise reach zero — and you skip a purchase order you would have regretted.

Automating the loop safely

Forecasting is worth automating; spending is worth automating only with constraints. If you let software create purchase orders, the safeguards matter more than the model:

  • A per-order ceiling that software cannot exceed.
  • A rolling daily cap across all automated actions, so no chain of individually reasonable orders adds up to an unreasonable day.
  • A cancellation window after approval — a minute is enough to catch the order you approved by reflex.
  • A data-eligibility rule: no automated action on SKUs without enough sales history to forecast. Acting on noise is worse than not acting.
  • A full audit trail of every proposal, approval and execution.

The goal is not an autonomous system. It is a system that does the arithmetic continuously, proposes the specific action with the dollars attached, and leaves the decision with the person who carries the consequences.