Berlin Tech Meetup: The Future of Relational Foundation Models, Systems, and Real-World Applications

Register now:
PyG/Use Case11 min read

Dynamic Pricing: Demand Graph Optimization

A 1% improvement in pricing yields 8-11% profit improvement, more than any other business lever. Traditional pricing optimizes products independently. Here is how to build a GNN that captures cross-product demand effects.

PyTorch Geometric

TL;DR

  • 1Dynamic pricing is a graph optimization problem. Products are connected by substitution and complementarity relationships, and price changes propagate through the network.
  • 2SAGEConv on a product-demand graph predicts demand as a function of price, capturing cross-product elasticity that independent demand models miss.
  • 3On demand prediction benchmarks, GNNs reduce MAPE by 20-30% vs flat-table LightGBM. Cross-product demand modeling, especially substitute and complement effects, accounts for the improvement.
  • 4The PyG model predicts demand; price optimization requires a separate optimization layer that maximizes portfolio margin subject to business constraints.
  • 5KumoRFM predicts demand at candidate price points with one PQL query, providing the demand model for portfolio-level optimization without graph construction.

The business problem

McKinsey research shows that a 1% improvement in pricing yields 8-11% improvement in operating profit, more than any other business lever. Yet most companies set prices using cost-plus rules or simple competitive matching. They miss the cross-product dynamics: raising the price of one product shifts demand to substitutes and affects the demand for complements.

Optimal pricing requires understanding the entire product demand network simultaneously. When Amazon changes the price of a laptop, it affects demand for laptop bags, mice, monitors, and competing laptops. These cross-product effects make pricing a graph problem.

Why flat ML fails

  • Independent optimization: Flat models optimize each product's price independently. Raising Product A's price might maximize A's margin but cannibalize B's demand, reducing total portfolio margin.
  • No cross-elasticity: The cross-price elasticity between substitutes is the most important pricing signal, and flat models cannot capture it without extensive feature engineering.
  • Competitive blindness: Competitor price changes affect your demand. The graph connects your products to competitor products, propagating competitive signals automatically.
  • Bundle effects: Complementary products have positive cross-elasticity: reducing the price of printers increases ink cartridge demand. These effects require graph-level reasoning.

The relational schema

schema.txt
Node types:
  Product     (id, cost, current_price, category, inventory)
  Competitor  (id, brand, market_share)
  Segment     (id, price_sensitivity, size, growth_rate)

Edge types:
  Product    --[substitute_of]-->  Product    (cross_elasticity)
  Product    --[complement_of]-->  Product    (bundle_lift)
  Product    --[competes_with]-->  Competitor (price_gap)
  Product    --[sold_to]-->        Segment    (volume, price_paid)

Substitute and complement edges encode cross-product demand effects. Competitor edges capture competitive dynamics.

PyG architecture: SAGEConv demand model

pricing_model.py
import torch
import torch.nn.functional as F
from torch_geometric.nn import SAGEConv, Linear

class PricingGNN(torch.nn.Module):
    def __init__(self, node_dim, hidden_dim=128):
        super().__init__()
        self.node_lin = Linear(node_dim, hidden_dim)

        # Aggregate substitute, complement, competitor signals
        self.conv1 = SAGEConv(hidden_dim, hidden_dim)
        self.conv2 = SAGEConv(hidden_dim, hidden_dim)

        # Demand prediction head: given price, predict units sold
        self.demand_head = torch.nn.Sequential(
            Linear(hidden_dim + 1, 64),  # +1 for candidate price
            torch.nn.ReLU(),
            Linear(64, 1),
        )

    def encode(self, x, edge_index):
        x = F.relu(self.node_lin(x))
        x = F.relu(self.conv1(x, edge_index))
        x = self.conv2(x, edge_index)
        return x

    def predict_demand(self, node_emb, candidate_price):
        # Concatenate embedding with candidate price
        h = torch.cat([node_emb, candidate_price.unsqueeze(-1)], dim=-1)
        return F.softplus(self.demand_head(h))  # demand >= 0

    def forward(self, x, edge_index, candidate_prices):
        z = self.encode(x, edge_index)
        demand = self.predict_demand(z, candidate_prices)
        return demand

# Portfolio optimization (post-GNN)
# For each product, evaluate demand at multiple price points
# Optimize: max Σ (price_i - cost_i) * demand_i(price_i)
# Subject to: inventory, competitor, and business constraints

The GNN predicts demand as a function of price and network context. Portfolio optimization runs on top, maximizing total margin across all products simultaneously.

Expected performance

Demand prediction for pricing is a regression task. The right metric is MAPE on held-out demand predictions:

  • Cost-plus pricing: Baseline (no demand model)
  • LightGBM (independent demand): ~15% MAPE
  • GNN (cross-product demand): ~10-12% MAPE
  • KumoRFM (zero-shot): ~10% MAPE

Or use KumoRFM in one line

KumoRFM PQL
PREDICT units_sold FOR product
USING product, sales, competitor, segment

One PQL query generates the demand model. Feed predictions at candidate price points into your optimization layer for portfolio-level pricing.

Frequently asked questions

How do GNNs improve dynamic pricing over traditional demand curves?

Traditional demand curves model each product independently. GNNs capture cross-product effects: raising the price of Coca-Cola shifts demand to Pepsi. The product graph encodes substitute and complement relationships, enabling the model to predict demand responses across the entire product network simultaneously.

What graph structure works for dynamic pricing?

Products are nodes with features (cost, current price, inventory). Edges connect substitutes (cross-price elasticity), complements (bundle affinity), and competitors (same category, similar specs). Temporal demand data on edges captures how price changes propagate through the product network.

How do you optimize prices across a product portfolio with GNNs?

Use the GNN to predict demand at candidate price points, then optimize the portfolio price vector to maximize total margin. The GNN captures cross-product effects, so raising one product's price and its impact on substitutes is modeled jointly. This is fundamentally different from optimizing each product independently.

How do you handle competitor pricing signals?

Add competitor products as nodes with scraped price features. Connect them to your products via substitute edges. The GNN learns how competitor price changes affect your demand, enabling responsive pricing strategies without manual rule creation.

Can KumoRFM optimize pricing?

KumoRFM can predict demand at different price points from your product, sales, and competitor data with a single PQL query. It captures cross-product elasticity automatically, providing the demand model needed for portfolio-level price optimization.

Learn more about graph ML

PyTorch Geometric is the open-source foundation for graph neural networks. Explore more layers, concepts, and production patterns.