mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-05-31 08:33:36 +00:00
feature: introducing pricing predictors (pricers)
This commit is contained in:
10
experiments/procesing/pricers/__init__.py
Normal file
10
experiments/procesing/pricers/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from procesing.pricers.base import PricingFunction
|
||||||
|
from procesing.pricers.elasticity import ElasticityBasedPricer
|
||||||
|
from procesing.pricers.simple import StaticPricer, RandomPricer
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PricingFunction',
|
||||||
|
'ElasticityBasedPricer',
|
||||||
|
'StaticPricer',
|
||||||
|
'RandomPricer'
|
||||||
|
]
|
||||||
28
experiments/procesing/pricers/base.py
Normal file
28
experiments/procesing/pricers/base.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
class PricingFunction(ABC):
|
||||||
|
"""
|
||||||
|
Abstract base for pricing functions.
|
||||||
|
Defines the mapping f: StateSpace -> prices
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def fit(self, historical_data: pd.DataFrame):
|
||||||
|
"""Train/calibrate the pricing function on historical data"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def predict(self, state_space) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Generate prices given current state space.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state_space: StateSpace object containing demand, prices, session features
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
prices: price vector P_{t+1} in R^n
|
||||||
|
"""
|
||||||
|
pass
|
||||||
55
experiments/procesing/pricers/elasticity.py
Normal file
55
experiments/procesing/pricers/elasticity.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from procesing.pricers.base import PricingFunction
|
||||||
|
|
||||||
|
|
||||||
|
class ElasticityBasedPricer(PricingFunction):
|
||||||
|
"""
|
||||||
|
Pricing based on demand elasticity estimates.
|
||||||
|
f(Q, S) = base_price * (1 + alpha * elasticity * demand_deviation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, alpha: float = 0.1, price_floor: float = 0.0, price_ceil: float = np.inf):
|
||||||
|
self.alpha = alpha
|
||||||
|
self.price_floor = price_floor
|
||||||
|
self.price_ceil = price_ceil
|
||||||
|
self.elasticity = None
|
||||||
|
self.base_prices = None
|
||||||
|
self.mean_demand = None
|
||||||
|
|
||||||
|
def fit(self, historical_data: pd.DataFrame):
|
||||||
|
"""
|
||||||
|
Calibrate from historical elasticity estimates.
|
||||||
|
Expects: [productId, elasticity, base_price, mean_demand]
|
||||||
|
"""
|
||||||
|
if 'elasticity' not in historical_data.columns:
|
||||||
|
raise ValueError("historical_data must contain 'elasticity' column")
|
||||||
|
|
||||||
|
self.elasticity = historical_data['elasticity'].values
|
||||||
|
self.base_prices = historical_data.get('base_price', np.ones(len(historical_data)) * 100).values
|
||||||
|
self.mean_demand = historical_data.get('mean_demand', np.ones(len(historical_data)) * 10).values
|
||||||
|
return self
|
||||||
|
|
||||||
|
def predict(self, state_space) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Adjust prices based on demand deviation and elasticity.
|
||||||
|
Higher demand -> increase price (but less for elastic goods)
|
||||||
|
"""
|
||||||
|
if self.elasticity is None:
|
||||||
|
raise ValueError("Must call fit() before predict()")
|
||||||
|
|
||||||
|
demand = np.asarray(state_space.demand)
|
||||||
|
if len(demand) != len(self.elasticity):
|
||||||
|
raise ValueError(f"Demand vector size {len(demand)} != elasticity size {len(self.elasticity)}")
|
||||||
|
|
||||||
|
# compute demand deviation from mean
|
||||||
|
demand_dev = (demand - self.mean_demand) / (self.mean_demand + 1e-6)
|
||||||
|
|
||||||
|
# adjust price: if demand high and elastic, don't increase much
|
||||||
|
# if demand high and inelastic, increase more
|
||||||
|
price_multiplier = 1 + self.alpha * np.abs(self.elasticity) * demand_dev
|
||||||
|
prices = self.base_prices * price_multiplier
|
||||||
|
|
||||||
|
# enforce bounds
|
||||||
|
prices = np.clip(prices, self.price_floor, self.price_ceil)
|
||||||
|
return prices
|
||||||
48
experiments/procesing/pricers/simple.py
Normal file
48
experiments/procesing/pricers/simple.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from procesing.pricers.base import PricingFunction
|
||||||
|
|
||||||
|
|
||||||
|
class StaticPricer(PricingFunction):
|
||||||
|
"""Static pricing: always return fixed base prices"""
|
||||||
|
|
||||||
|
def __init__(self, base_prices: np.ndarray = None):
|
||||||
|
self.base_prices = base_prices
|
||||||
|
|
||||||
|
def fit(self, historical_data: pd.DataFrame):
|
||||||
|
"""Extract base prices from historical data"""
|
||||||
|
if 'base_price' in historical_data.columns:
|
||||||
|
self.base_prices = historical_data['base_price'].values
|
||||||
|
elif 'price' in historical_data.columns:
|
||||||
|
self.base_prices = historical_data['price'].values
|
||||||
|
else:
|
||||||
|
raise ValueError("historical_data must contain 'base_price' or 'price' column")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def predict(self, state_space) -> np.ndarray:
|
||||||
|
"""Return static base prices regardless of state"""
|
||||||
|
if self.base_prices is None:
|
||||||
|
raise ValueError("Must call fit() or provide base_prices in constructor")
|
||||||
|
return self.base_prices.copy()
|
||||||
|
|
||||||
|
|
||||||
|
class RandomPricer(PricingFunction):
|
||||||
|
"""Random pricing within bounds (for baseline comparison)"""
|
||||||
|
|
||||||
|
def __init__(self, price_min: float = 50.0, price_max: float = 500.0, seed: int = None):
|
||||||
|
self.price_min = price_min
|
||||||
|
self.price_max = price_max
|
||||||
|
self.seed = seed
|
||||||
|
self.n_products = None
|
||||||
|
self.rng = np.random.default_rng(seed)
|
||||||
|
|
||||||
|
def fit(self, historical_data: pd.DataFrame):
|
||||||
|
"""Learn number of products"""
|
||||||
|
self.n_products = len(historical_data)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def predict(self, state_space) -> np.ndarray:
|
||||||
|
"""Generate random prices"""
|
||||||
|
if self.n_products is None:
|
||||||
|
self.n_products = len(state_space.demand)
|
||||||
|
return self.rng.uniform(self.price_min, self.price_max, size=self.n_products)
|
||||||
Reference in New Issue
Block a user