Pricing Manager

Complete business logic documentation for the tradeit.gg pricing engine — every formula, threshold, and decision path

1. Overview & Data Flow

The Pricing Manager runs as a NestJS service that determines prices for every tradeable item (CS2/TF2/Rust skins). It produces these outputs:

Bot Trade Price
Price our bots use when acquiring items from other bots/services. Higher = we want the item more.
Internal use only
Player Trade Price
Price we pay users when they deposit (sell to us). Lower = we're cautious about this item.
Visible to users depositing items
Store Price
Price users pay when buying from our store. Based on stable price + markup + stock level.
Visible to users buying items
Instant Sell Price
Quick-sell price for users who want immediate cash out. Fraction of the player trade price.
Visible to users selling for balance
All prices are in cents (integers). A value of 22100 = $221.00.

Running Example: AK-47 | Redline (Field-Tested)

We'll follow this item through every step of the pricing pipeline to show how each calculation works with real numbers.

stablePrice: 2,210 ($22.10)
livePrice: 2,300
minPrice: 2,100
buffBuy: 1,800
buffSell: 2,000
buffSellAmount: 35
botStock: 15
currentStock: 18
wantedMaxStock: 30
neededStock: 20
monthlyOut: 40
weeklyOut: 12
monthlyIn: 35
sumQuantity: 150
margin: 0.75
marginGroup: 12

Think of the Pricing Manager as a shopkeeper who needs to decide three things for every item on the shelf:

The system recalculates all prices periodically by running through a 13-step pipeline. Each step adds more information or applies adjustments based on market conditions, inventory levels, and risk signals.

How the 4 prices relate to each other:

Market Data
stablePrice
baseBotPrice
+ adjustments
Bot Trade Price
basePlayerPrice
+ adjustments
Player Trade Price
+ markup + stock
Store Price

Processing Pipeline

1 Fetch Buff prices
2 Add inventory stocks
3 Fetch market prices
4 Calculate stable price
5 Assign margin groups
6 Merge trade stats
7 Calculate base prices
8 Add previous prices
9 Add EOD medians
10 Calculate stocks
11 Price adjustments
12 Protections
13 Save to DB

2. Data Sources

Before calculating any price, we need raw ingredients from 4 sources. Think of it like a chef gathering ingredients before cooking:

Data flows into the pricing engine:

Buff.163
Market Aggregator
Inventory (item_stocks)
Trade Stats
Pricing Engine
Bot Price
Player Price
Store Price
Instant Sell
Running Example

For our AK-47 Redline: Buff says buy=$18, sell=$20 with 35 listings. The aggregator shows prices on 4 markets (Steam $23, CSFloat $22, Buff $21, Halo $21.50). Our bots hold 15 units. Monthly: 40 withdrawals, 35 deposits.

Buff.163 (BuffPriceScraperService)

Fetches from the buff_prices_scraper table (appId = 730). Provides:

FieldDescription
buffBuyHighest buy order price on Buff
buffSellLowest sell listing price on Buff
buffBuyAmountNumber of buy orders
buffSellAmountNumber of sell listings
isDopplerWhether this item is a Doppler knife variant

Market Aggregator (CsgoskinsPricesFetcherService)

HTTP GET to external aggregator. Returns per-item array of market prices from: Steam, CSFloat, Buff163, Halo, and others. Each entry has: market, price, quantity, volume.

Inventory Stocks (ItemStocksService)

From the tradeit database item_stocks table:

FieldDescription
botStockItems held by our bots
containerBotStockItems in container bots
tradableContainerBotStockTradeable items in container bots
totalTradableContainerBotStockTotal tradeable container bot items (all containers)
reservedItemsStockReserved items total
tradeableReservedItemsStockReserved items that are tradeable
lockedReservedItemsStockReserved items that are locked
tempReservedItemsStockTemporarily reserved
userListingsStockItems listed by users (not ours)

currentStock is calculated as:

currentStock = max((botStock + containerBotStock + tradableContainerBotStock) - (lockedReservedItemsStock + userListingsStock), 0)
Why subtract? Locked and user-listing items aren't available for trading. Only freely tradeable inventory counts toward our stock level.

Trade Statistics (TradeItemStats)

PeriodFields
MonthlymonthlyInAmount, monthlyOutAmount, monthlyInUniqueAmount, monthlyOutUniqueAmount, monthlyInAvgPrice, monthlyOutAvgPrice
WeeklyweeklyInAmount, weeklyOutAmount, weeklyInAvgPrice, weeklyOutAvgPrice
DailydailyInAmount, dailyOutAmount, dailyInAvgPrice, dailyOutAvgPrice

"In" = items deposited to us (users sell). "Out" = items withdrawn from us (users buy). "Unique" = distinct users.

EOD Medians (ItemEODService)

Calculated from hourly snapshots stored in item_prices_snapshot (up to 100 per item):

FieldDescription
avg7DStablePriceAverage stable price over last 7 days
prevMonthAvg7DStablePriceAverage stable price from 30-37 days ago
median7DStablePriceMedian stable price over 7 days
median30DStablePriceMedian stable price over 30 days
median7DMaxStockMedian wantedMaxStock over 7 days
median30DMaxStockMedian wantedMaxStock over 30 days

3. Stable Price Calculation

The stable price is the foundation of all other prices. It smooths out short-term market noise and manipulation by averaging across markets and time. Without it, a single marketplace spike or a low-liquidity item could cause our prices to swing wildly.

Imagine you're checking the price of an item across 5 different stores. One store has it at $25, another at $22, one sketchy store says $50, and two say $21. You wouldn't use $50 as your reference — you'd filter out the outlier and average the rest.

That's exactly what stable price does: it collects prices from multiple marketplaces (Steam, CSFloat, Buff, Halo), filters out the top 30% of prices (outliers), weights by quantity (a store with 100 units is more reliable than one with 3), and then smooths this over 30 days of snapshots.

Stable Price Pipeline:

All Markets
Filter (qty>2, no tradeit)
70th Percentile Cut
Weighted Avg = livePrice
Liquidity Adjust
30d Snapshot Mean
Spike Protection
stablePrice
Running Example

Our AK-47 Redline has prices from 4 markets: Steam ($23), CSFloat ($22), Buff ($21), Halo ($21.50). After filtering and weighting, livePrice = 2,300. The 30-day snapshot mean is 2,200, so stablePrice = 2,210. The 7-day average (2,150) is within 30% of stable, so no spike protection triggers.

Step 1: Filter Markets

Keep only markets where: price > 0, quantity > 2, market != 'tradeit_gg'

Step 2: Top 70th Percentile Filter

Keep markets where: price ≤ 70th percentile OR (Steam AND price > 0)

Step 3: Calculate Live Price

livePrice = ceil( sum(price * weight) / sum(weights) )
weight = min(quantity, 100) for non-Steam; 0 for Steam
weight cap at 100: Prevents a marketplace flooding 10,000 cheap listings from dominating the average. Steam excluded: Steam prices include tax and are systematically higher — used as a reference but not in the weighted average.

Step 4: Adjust Live Price by Liquidity

adjustedLivePrice = floor( minPrice + (livePrice - minPrice) * min(1, sumQuantity / 30) )
Why 30? At <30 total units across markets, liquidity is too low for a reliable weighted average. The formula pulls livePrice toward minPrice proportionally — at 15 units, only half the live-min gap is used. At 30+, full live price.

Step 5: Stable Price

stablePrice = ceil( 30-day snapshot mean ) OR livePrice if no snapshots
Why 30 days? Long enough to smooth daily volatility, short enough to respond to genuine trends. Hourly snapshots (up to 100 per item) feed the mean. New items with no history fall back to livePrice.

Step 6: Stable Price Protection

For items with stablePrice > 1000 ($10): if price moved >30% from historical avg, use avg7DStablePrice instead.

avgTop = max(avg7D, avg37D); avgBot = min(avg7D, avg37D)
if stablePrice > avgTop * 1.3 OR stablePrice < avgBot * 0.7: use avg7DStablePrice
30% band (1.3x / 0.7x): If stable price jumped or dropped >30% vs both the 7-day and 30-37 day averages, it's likely a data anomaly or manipulation. Fall back to the 7-day average as a safer reference.

Step 7: Store Base Price (fallback chain)

  1. steamApiPrice
  2. (2 * maxPrice + stablePrice) / 3
  3. buffApiPrice * 1.5
  4. buffBuy * 1.5
  5. 0
Stable Price Calculator

4. Pricing Groups & Margins

Different price tiers need different margins. A $0.10 skin and a $500 knife carry very different risk profiles. Pricing groups assign each item a margin (our spread between buy and sell) and a group tier used for popularity ranking. This ensures we don't apply the same profit logic to cheap commodity skins and rare expensive items.

Just like a jewelry store has higher markups than a supermarket, we apply different margins at different price points. A $0.50 sticker might have a 150% margin (we pay $0.20, sell for $0.50) while a $500 knife might only have 30% (we pay $385, sell for $500). The pricing group table maps price ranges to margins.

Running Example

Our AK-47 at stablePrice $22.10 falls into marginGroup 12 with margin 0.75. This means the player trade price will be roughly botPrice / 1.75 — about 57% of the bot price. The group number (12) also determines the rank multiplier cap (1.15 since it's below 30).

Items are assigned a margin and marginGroup based on their stable price. The pricing_groups table defines price tiers.

Lookup: find the last row where minPrice ≤ stablePrice. That row's margin and group are assigned.

5. Base Price Calculation

Base prices are the starting point before any stock or market adjustments. Popular items (high market quantity and high withdrawal rate) get a higher multiplier because they're easier to resell. Unpopular items get a lower multiplier because they carry more risk of sitting in inventory. This ranking happens within each price tier so a popular cheap skin doesn't compete with an unpopular expensive one.

Not all $22 items are equal. An AK-47 Redline that 40 people buy per month is much safer to hold than a niche sticker with the same price but only 2 buyers. The system ranks items by popularity within their price tier and gives popular items a bonus multiplier (up to 1.5x) and unpopular ones a penalty (down to 0.9x).

The bot price is what we'd pay to acquire it from bots. The player price is what we'd pay a user — always lower than bot price (the gap is the margin, set by the pricing group).

Popularity ranking within a price group:

0.9x
Least popular
0.95x
1.0x
Average
1.05x
AK-47 Redline
1.15x
Most popular
Running Example

Our AK-47 Redline is in marginGroup 12 with margin 0.75. It's a popular item (popularity = 150 * 40 = 6,000), ranking high in its group. It gets rankMultiplier = 1.05.

baseBotTradePrice = ceil(2,210 * 1.05) = 2,321

basePlayerTradePrice = floor(2,321 / 1.75) = 1,326

So before any adjustments, we'd pay bots $23.21 and users $13.26 for this item.

Popularity Ranking

popularity = sumQuantity * monthlyOutAmount
Why multiply? Items that are both widely available (high sumQuantity) AND frequently bought (high monthlyOut) are safest to hold. An item with 1,000 market listings but 0 sales is dangerous. One with 50 sales but only 5 listings is volatile. The product captures both.

Items grouped by marginGroup, sorted by popularity (descending). Rank goes from 1 (least) to N (most popular).

Rank Multiplier

MarginGroupCap Multiplier
< 301.15 (High)
≥ 301.5 (Extreme)
rankMultiplier = 0.9 + (capMultiplier - 0.9) * (rank - 1) / (totalItems - 1)
Linear interpolation 0.9 to cap: Even the least popular item gets 90% of stable price (not punished too hard). The most popular item gets up to 1.15x (or 1.5x for very expensive groups). This spread incentivizes holding popular items without making unpopular ones unsellable.

Base Prices

baseBotTradePrice = ceil(stablePrice * rankMultiplier)
basePlayerTradePrice = floor(baseBotTradePrice / (1 + margin))
ceil vs floor: Bot price rounds up (we pay slightly more to be competitive), player price rounds down (we pay slightly less for safety). The margin gap between them is our gross profit per trade.
Base Price Calculator

6. Stock Calculations

Stock targets determine how many of each item we want to hold. Holding too much ties up capital and exposes us to price drops. Holding too little means we can't fulfill trades. The system calculates a target stock level from trading activity, then applies 5 protection layers to prevent overstocking due to manipulation, market share concerns, or demand spikes.

Think of stock targets like a grocery store deciding how many bananas to keep on the shelf. Too many and they go bad (price drops). Too few and customers leave empty-handed. The system looks at how fast items move (in and out) and sets a target. Then 5 safety checks run to make sure we're not being tricked into holding too much.

Stock Protection Funnel: each layer can only reduce, never increase

Default Max Stock
50
Unique Anomaly
50
Market Share
50
Low Capacity
50
Spike Factor
50
In/Out Ratio
30
Final wantedMaxStock
30
Running Example

For our AK-47: monthlyOut=40, weeklyOut=12, monthlyIn=35, sumQuantity=150. Default max stock calculates to ~38. No protections trigger (price isn't cheap, unique ratio is healthy, market share is fine), except in/out ratio is slightly unfavorable — capping at wantedMaxStock = 30. With currentStock=18, we have room: maxDeposit = 30 - 18 = 12 more units.

6a. Default Max Stock

The raw estimate based on how fast items move in and out. Averages multiple signals (monthly out, unique users, weekly trend, monthly in) to get a balanced view rather than relying on any single metric.

How many should we hold? The system asks 5 questions and averages the answers: How many sold last month? How many unique buyers? What's the weekly trend extrapolated? Does the unique-buyer count seem suspiciously low? How many are coming in? The average gives a starting target before safety caps.

Running Example

Components: [monthlyOut=40, uniqueOut=30, weeklyTrend=48, monthlyIn=35]. Average = (40+30+48+35)/4 = 38.25. sumQuantity=150 > 10 so minimum is 1. defaultMaxStock = 38.

Raw estimate of how many units to hold, before protection caps.

Components (averaged):

  1. monthlyOutAmount
  2. monthlyOutUniqueAmount
  3. weeklyOutTrend = weeklyOut * 4 (capped to monthlyOut if weekly/monthly > 0.8, or if weekly/monthly > 0.3 AND unique ratio < 0.3)
  4. Extra monthlyOutUniqueAmount if unique/monthly ratio < 0.2
  5. adjustedMonthlyIn = monthlyIn (capped to monthlyOut if monthlyIn > 20 AND monthlyIn/monthlyOut > 2)
Cap logic: Weekly trend is extrapolated (×4) but capped when diverging from monthly (>80% ratio = one anomalous week). Unique ratio <0.2 means 1-2 whales did 80%+ of withdrawals — weight unique count extra. Monthly inflow capped at outflow if deposits are 2x+ withdrawals (we're accumulating too fast).
defaultMaxStock = max( average(all components), sumQuantity > 10 ? 1 : 0 )
Floor of 1: If an item exists on >10 market listings, we want at least 1 unit so we can offer it. Items with ≤10 listings are too rare to stock proactively.
Default Max Stock Calculator

6b. Stock Protections (5 Layers)

The default max stock can be fooled by whales, price manipulation, or market anomalies. Each protection catches a specific failure mode. They run independently and the most restrictive one wins (min of all), ensuring that if ANY signal says "danger", we reduce our stock target.

Each protection asks a specific question:

  1. Unique Anomaly: "Are a few whales inflating the withdrawal count?" If 3 users did 90% of withdrawals, cap at unique user count.
  2. Market Share: "Would we own too much of the total market?" Don't hold >50% (or >30% for expensive items).
  3. Low Capacity: "Is this a cheap item eating all our capacity?" Reduce stock for sub-$5 items with 100+ target.
  4. Spike Factor: "Did demand suddenly double vs the 30-day median?" Probably artificial — cap near the median.
  5. In/Out Ratio: "Are items coming in faster than going out?" If so, reduce proportionally.
Running Example

For our AK-47 (defaultMaxStock=38): Unique ratio is 30/40=0.75 (healthy, >0.3) → no anomaly. Market share: 38/150=25% (under 50%) → OK. Price $22 is not <$5 → no low capacity. No demand spike vs median → OK. In/out ratio: slightly unfavorable at 0.7 → caps to 38*0.75=29, rounded to 30.

Each protection caps the value. Final wantedMaxStock = ceil(min(all 5)).

1. Unique Anomaly PROTECTION

If monthlyOut > 20 AND sumQuantity < 400 AND uniqueRatio < 0.3 AND buffSellAmount < 50: cap at monthlyOutUniqueAmount

2. Market Share PROTECTION

3. Low Capacity PROTECTION

If defaultMaxStock > 100 AND stablePrice < 500: multiply by wantedMaxStockLowCapacityRatio (admin config)

4. Spike Factor PROTECTION

If dms > 20 AND dms/median30DMaxStock > 2 AND (price doubled OR unique ratio < 0.3): cap at median30DMaxStock * 1.2

5. In/Out Ratio PROTECTION

globalRatio = weeklyRatio * weight + monthlyRatio * (1 - weight)
Blended ratio: Weekly data is fresher but noisier. Monthly is more stable but slower. The admin-controlled weight lets operators decide how reactive vs stable the system should be. A ratio of 0.5 means equal influence.
globalRatioMultiply by
0.6 – 0.80.75
0.4 – 0.60.50
0 – 0.40.25
≥ 0.81.0 (no change)

Only when: stablePrice > 2000 AND (totalInflow > 50 OR currentStock > 30)

Stock Protections Calculator

6c. Stock Buffer

For very cheap, very high-demand items where we're already over target: instead of aggressively dumping, allow a small controlled buffer (5% extra). This prevents constant churn of blocking then unblocking deposits for items that move fast.

Running Example

Our AK-47 at $22.10 does NOT qualify for buffer (needs stablePrice < $1 AND wantedMaxStock > 200). Buffer is only for cheap, high-volume items like $0.03 stickers where we might hold 500+ units.

Allows controlled overstocking for cheap high-demand items. ALL conditions:

potentialIncrease = max(currentStock * 1.05, currentStock + 50)
buffer = max(0, potentialIncrease - wantedMaxStock)

6d. Final Stock Values

Running Example

neededStock = min(ceil(avg(40/4, 30/4, 12) * 1.45), 30) = min(ceil(11.08 * 1.45), 30) = min(17, 30) = 17

dailyMaxStock = max(30/4, 1) = 8 (accept max 8 per day)

maxDeposit = 30 - 18 = 12 (room for 12 more units)

neededStock = min( ceil( avg(monthlyOut/4, monthlyOutUnique/4, weeklyOut) * 1.45 ), wantedMaxStock )
dailyMaxStock = max( wantedMaxStock / 4, 1 )
maxDeposit = wantedMaxStock - currentStock
÷4: Converts monthly/unique amounts to weekly equivalents. ×1.45: 45% buffer so we don't run out during demand spikes. dailyMaxStock = wantedMax/4: Allows at most ~1 week's worth of deposits per day. maxDeposit: Simple gap between target and current — negative means we're overstocked.

7. Price Adjustments

Base prices assume a static world. Adjustments layer on real-time signals: inventory levels, market data quality, price trends, and anomalies. Each adjustment runs independently and produces a multiplier. They're combined conservatively in step 8: bot takes the most aggressive increase, player takes the most aggressive decrease.

Base prices are like the MSRP on a car. But the actual price you pay depends on supply and demand, the dealer's inventory, market conditions, and whether the model is being discontinued. Adjustments are these real-world factors.

There are 9 independent adjustments, each watching a different signal. Some affect what we pay bots, some affect what we pay users, some affect both. At the end, they're combined by taking the most aggressive value — if any one signal says "danger", we act on it.

The 9 Adjustments at a glance:

7.1 Deficit BOTPLAYER
7.2 Cheap Item PLAYER
7.3 Deposit Burst BLOCK
7.4 Buff Spread PLAYER
7.5 Low Buff PLAYER
7.6 Monthly Avg BOTPLAYER
7.7 Live/Stable BOTPLAYER
7.8 Prev Month PLAYER
7.9 Price Spike BLOCK

BOT = affects bot price   PLAYER = affects player price   BLOCK = can fully block deposits

Nine independent adjustments. Each produces a multiplier for bot and/or player price.

7.1 Deficit Adjustment BOT PLAYER

The core inventory balancer. If we have too few of an item, raise prices to attract deposits and slow withdrawals. If too many, lower prices to push items out and discourage new deposits. The asymmetry (4.5% vs 10%) is intentional: we're more aggressive at reducing player prices when overstocked than increasing bot prices when understocked.

This is the most important single adjustment. It measures "are we under or over our target stock?" and translates that into a price nudge. The further from balanced, the bigger the nudge. At -1 (full overstock), we aggressively discount. At +1 (completely empty), we pay a premium to attract items.

Deficit Scale:

-1 Full Overstock
-0.5 Overstock
0
+0.5 Understocked
+1
Dump inventory, block deposits Balanced Attract more items
Running Example

Our AK-47: currentStock=18, neededStock=20. We're slightly understocked.

deficit = (20 - 18) / 20 = 0.10 (10% understocked)

Bot change: 10% * 0.10 = +0.01 (1% increase to attract items)

Player change: 4.5% * 0.10 = +0.0045 (0.45% increase, gentle)

Result: slightly higher prices to encourage deposits and slow withdrawals.

Step 1: Calculate Deficit (-1 to +1)

Scaling: If maxStock < 4: deficit *= maxStock / 4

Step 2: Price Changes

Overstocked (deficit < 0)Understocked (deficit > 0)
BOT4.5% * deficit (small decrease)10% * deficit (big increase)
PLAYER10% * deficit (big decrease)4.5% * deficit (small increase)
Deficit Calculator

7.2 Cheap Item Overstock PLAYER

For items priced at $0.03-$0.50, percentage adjustments round to zero and have no effect. This adds a fixed cent deduction (1-3 cents) that actually moves the needle on cheap items.

Running Example

Our AK-47 at $22.10 is not cheap (stablePrice > $1). Deficit is positive (understocked). Result: 0 cents deducted. This only matters for sub-$1 overstocked items.

ConditionCents
deficit between -0.5 and -1+1
deficit = -1 (full overstock)+2
stablePrice < 100+1 additional
Cheap Item Calculator

7.3 Daily Deposit Burst BLOCK

Prevents someone from dumping a large quantity of one item in a single day. If today's deposits already hit 25% of our desired max stock, block further deposits until tomorrow.

Running Example

AK-47: wantedMaxStock=30 (>30? No). Even if it were, dailyMaxStock=8 and suppose only 3 deposited today. Result: not blocked. Would only trigger for high-stock items getting flooded.

blocked = (wantedMaxStock > 30 AND dailyInAmount ≥ dailyMaxStock) ? -1 : 0
wantedMaxStock > 30: Only applies to items we hold in bulk. Low-stock items (expensive) aren't subject to daily burst limits. dailyMaxStock: Roughly 25% of wanted max — allows us to restock in ~4 days, not all at once.
Deposit Burst Calculator

7.4 Buff Spread Analysis PLAYER

A wide spread between Buff buy and sell orders signals an illiquid or manipulated item. If the buy/sell gap is large, it means nobody agrees on the price and we could get stuck holding it. This caps our buy price or blocks deposits entirely for risky items.

Running Example

AK-47: buffBuy=1,800, buffSell=2,000. Spread = 2000/1800 = 1.11. At stablePrice $22 (2,210 cents, > $10), the threshold for wide spread is 1.5. Since 1.11 < 1.5: no change. If spread were 1.6 (e.g., buy=$18, sell=$29), it would cap our player price.

#ConditionAction
1(no buff data) AND stablePrice > 200000DUMP (-1)
2buffBuy*2 < stablePrice AND stablePrice > 15000DUMP (-1)
3buffSell = 0No change
4buffBuy = 0 AND sumQuantity > 30No change
5buffBuy = 0 AND sumQuantity ≤ 30DUMP (-1)
6stablePrice > 100000 AND spread > 1.3Cap at buffBuy*1.75
7stablePrice > 1000 AND spread > 1.5Cap at buffBuy*1.75
$2,000 no-buff threshold: Expensive items with zero Buff data are too risky to price. buffBuy×2 < stable: If the highest Buff buyer would pay less than half our price, we're likely wrong. Spread 1.5x vs 1.3x: Tighter threshold for expensive items because the dollar loss on a bad trade is larger.
Buff Spread Calculator

7.5 Low Buff Sell Listings PLAYER

Few Buff sell listings means the item is hard to price accurately and hard to resell. If we can't verify fair market value with enough data points, reduce exposure by capping what we pay or blocking deposits.

Running Example

AK-47: buffSellAmount=35 (≥10), sumQuantity=150 (≥20). Neither dump condition met. buffRisk = 1,326 / (1,800*1.75) = 0.42 (< 1.3). No change. This fires for rare items with <10 Buff listings.

#ConditionAction
1stablePrice > 1000 AND buffSellAmount < 10 AND sumQuantity < 20DUMP
2buffSell=0 AND buffBuy=0No change
3sumQuantity < 30 AND buffRisk > 1.3Cap at buff*1.75
buffRisk = basePlayerTradePrice / ((buffBuy OR buffSell) * 1.75)
1.75x trade multiplier: Our standard markup from Buff price to trade price. If we're paying >1.3x that already-marked-up price, we're overpaying relative to what Buff's market data supports.
Low Buff Listings Calculator

7.6 Monthly Price Average BOT PLAYER

Compares what we actually paid/received in real trades vs our configured base prices. If we're consistently paying more than our bot price to acquire items, we need to raise it. If we're paying users far more than what items actually sell for, we need to lower the player price. Reality-check against our own trading data.

Running Example

Suppose our AK-47 has monthlyInAvgPrice=2,400 and baseBotPrice=2,321. Is 2,400 > 2,321*1.03 (2,391)? Yes → botChange = +0.004. For player side, monthlyOutAvgPrice=1,200, and 1,200*2=2,400 is not < 1,326, so no player change.

Bot increase

weightedInAvg = weeklyInAvg > 0 ? (monthlyInAvg + weeklyInAvg)/2 : monthlyInAvg
if weightedInAvg > baseBotTradePrice * 1.03: botChange = abs(1 - weightedInAvg*1.03/baseBotPrice)
3% threshold: Small fluctuations are normal. Only raise the alarm if we're consistently paying 3%+ more than our configured price — that means the base price is stale and needs updating.

Player decrease (two paths, take largest)

Path A: if weightedOutAvg * 2 < basePlayerPrice: change = 1 - min(weightedOutAvg*0.97/basePlayerPrice, 1)
Path B: if minPrice*3 < basePlayerPrice AND markets > 3: change = 1 - min(minPrice*1.75/basePlayerPrice, 1)
playerChange = -max(pathA, pathB)
Path A (outAvg×2): If items sell for less than half our player price, we're massively overpaying users. Path B (minPrice×3): If the cheapest listing across 4+ markets is 3x below our price, the market clearly disagrees with our valuation. Take the larger reduction — both signals are serious.
Monthly Price Avg Calculator

7.7 Live-to-Stable Price Ratio BOT PLAYER

The stable price is smoothed and lags behind sudden market moves. If the live market price crashes 20%+ below our stable price, we're paying too much. This reduces our buy price to match reality. Conversely, if our stable price is lagging behind a genuine price increase, it boosts the bot price so we don't miss acquisition opportunities.

Running Example

AK-47: stablePrice=2,210, livePrice=2,300, avg7D=2,150. live/stable = 2,300/2,210 = 1.04 (not < 0.8). stable/max(avg7D,prevMonth) = 2,210/2,150 = 1.03 (not < 0.8 either). No change. This fires when live price crashes — e.g., if a major sale event drops live to $15 while stable is still $22.

Only when adjustByLiveToStablePriceRatio = '1'.

Bot boost

if stablePrice > 5000 AND stablePrice/maxAvg < 0.8: botChange = (maxAvg/stablePrice - 1) * modifier
0.8 ratio (>$50 items): If our stable price is 20%+ below the recent average, it's lagging behind a genuine price increase. The modifier controls how aggressively we correct — full modifier would match the gap entirely.

Player reduction (two paths)

Path A: if live/stable < 0.8 (both > 5000): change = 1 - live/stable
Path B: if stable/lowestMarket > 1.3 (stable > 2000, min > 1000): change = (1 - lowest/stable) * modifier
playerChange = -max(pathA, pathB)
Path A (live crash): Live price dropped 20%+ below stable — we're overpaying vs current reality. Path B (market gap): Our stable is 30%+ above the cheapest real listing — the market has moved on. lowestMarket = max(minPrice, buffSell): Takes the higher of the two as a more generous reference. Modifier lets admins tune aggressiveness.
Live-to-Stable Calculator

7.8 Previous Month Price Average PLAYER

Catches items where our stable price has drifted far above where it was historically. This can happen through gradual manipulation or stale data. By comparing current stable price against the 30-day median and Buff data, we detect items that are priced unrealistically high and reduce or block deposits before we overpay.

Running Example

AK-47: stablePrice=2,210, buffBuy=1,800. Is stable/buff > 2? 2,210/1,800 = 1.23 (no, < 2). Not triggered. This fires for items where stable is 2x+ Buff — a strong sign our price is inflated vs the real market.

Triggers when: stablePrice > 300 AND stablePrice/buffBuy > 2 AND median30D > 1 AND (stable/avg7D > 1.3 OR stable/prevMonth > 2)

If block flag ON: -1. Otherwise: -(1 - min(median30D/stablePrice, 1))

Prev Month Avg Calculator

7.9 Price Spike Detection BLOCK

Detects sudden price spikes that could be market manipulation (pump and dump). If either the live or stable price jumped 30%+ above the 7-day average, block all deposits immediately. Better to miss a legitimate spike briefly than to accept items at an inflated price.

Running Example

AK-47: stablePrice=2,210, avg7D=2,150. Is stable > avg7D*1.3 (2,795)? No. Is live (2,300) > 2,795? No. Not blocked. If someone manipulated the market and stable jumped to $30 while avg7D was still $22, this would immediately block deposits.

if flag ON AND stablePrice > 1000 AND (live > avg7D*1.3 OR stable > avg7D*1.3): playerChange = -1
30% spike threshold: A 30% jump in one cycle vs the 7-day average is abnormal. Could be pump-and-dump manipulation. >$10 minimum: Cheap items fluctuate more naturally — only flag spikes on items worth enough to manipulate. Block first, verify later — reopening deposits costs nothing, overpaying is permanent.
Price Spike Calculator

8. Combining Adjustments

Multiple adjustments may fire simultaneously. The combining logic reflects our risk posture: for bot prices (what we pay to acquire), be aggressive — take the biggest increase. For player prices (what we pay users), be conservative — take the biggest reduction. One red flag is enough to lower what we pay.

How adjustments combine:

Bot Price (MAX wins)
byDeficitBot: +0.01
byMonthlyAvgBot: +0.074
byAvg7DBot: 0
Winner: +0.074 (most aggressive increase)
Player Price (MIN wins)
byDeficitPlayer: +0.005
byBuffSpread: 0
byMonthlyAvg: -0.12
bySpike: -1
Winner: -1 (most aggressive reduction)

The asymmetry is deliberate. For buying (bot price), we want to be competitive — if any signal says we should pay more to acquire, we do. For paying users (player price), we're paranoid — if even one signal says this item is risky, we reduce what we pay. Better to miss a few deposits than to overpay on a manipulated item.

Running Example

AK-47 adjustment results: byDeficitBot=+0.01, byMonthlyBot=+0.004, byAvg7DBot=0. Bot: MAX = +0.01

byDeficitPlayer=+0.005, all other player adjustments=0 (healthy item!). Player: MIN = +0.005 (the only non-zero value, which is actually positive — slight price increase since we're understocked)

This AK-47 is a healthy, popular item — most protections don't fire. That's the normal case.

botPriceChange = MAX(byDeficitBot, byMonthlyPriceAvgBot, byAvg7DStablePriceRatioBot) [non-zero only]
playerPriceChange = MIN(byDeficitPlayer, byBuffSpreadPlayer, byLowBuffSellListingsPlayer, byMonthlyPriceAvgPlayer, byPriceSpikePlayer, wantedMaxStockBuffer, byPrevMonthPriceAvgPlayer, byLiveToStablePriceRatioPlayer) [non-zero only]
MAX for bot: If any signal says we should pay more, do it — being under-priced means losing items to competitors. MIN for player: If any signal says danger, take the most aggressive reduction — one bad signal is enough to protect against overpaying. Non-zero filter: Signals that returned 0 (didn't fire) are excluded so they don't dilute the active signals.
Zero values excluded. If all zero, result is 0.

9. Final Prices

This is where everything comes together. Base prices are multiplied by the combined adjustments and admin markups to produce the 4 final prices that users and bots see. Each price has its own formula and purpose.

Now all the data and adjustments merge into 4 final numbers. The store price has an interesting twist: if we're massively overstocked on an item AND the market price is reasonable, we switch to liquidation mode — undercutting the cheapest market listing to move inventory fast, even if it means lower margins.

Store Price Decision Tree:

botStock > 20?
AND maxDeposit < -30? (way over capacity)
AND minPrice close to stablePrice?
LIQUIDATION: undercut market by 1% (sell fast)
NORMAL: stablePrice * (1 + deficit) * (1 + 7dAvg) * (1 + markup), floored at minPrice
Running Example

Our AK-47: botStock=15 (not > 20), so liquidation does NOT trigger. Normal mode:

storePrice = max(round(2,210 * 1.01 * 1.0 * 1.05), 2,100) = max(2,344, 2,100) = 2,344 ($23.44)

Bot price: floor(2,321 * 1.01 * 1.05) = 2,461 ($24.61)

Player price: floor(1,326 * 1.005) = 1,332 ($13.32)

Instant sell: round(1,332 / 1.75 * 0.85) = 647 ($6.47)

Bot Trade Price

finalBot = floor(baseBotTradePrice * (1 + botPriceChange) * (1 + tradePriceMarkup))

Player Trade Price

finalPlayer = floor(basePlayerTradePrice * (1 + playerPriceChange))
if decreaseDepositForCheap ON: finalPlayer = max(finalPlayer - cheapItemAdj, 1)

Store Price — Liquidation Mode (all 3 must be true)

liquidationStore = max(minPrice - max(round(minPrice * 0.01), 1), 1)
1% undercut: Price our store 1% below the cheapest competitor to move inventory fast. Minimum 1 cent: For cheap items where 1% rounds to 0. Floor at 1: Never price at 0 cents.

Store Price — Normal Mode

normalStore = max(round(stablePrice * (1+byDeficitBot) * (1+byAvg7DBot) * (1+storePriceMarkup)), minPrice)
Three multipliers stack: Deficit adjusts for stock level (overstock=discount, understock=premium). 7D average adjusts for trend. Markup is the admin-set profit margin. Floor at minPrice: Never sell below the cheapest market listing — if we did, arbitrageurs would buy from us and resell.

Instant Sell Price

pricingMultiplier = baseBotTradePrice / max(stablePrice, 1)
divisor = pricingMultiplier > 1 ? pricingMultiplier : 1.75
instantSell = round(finalPlayer / divisor * (instantSellAdjust% / 100))
pricingMultiplier: How much above stable the bot price is (reflects popularity). 1.75 fallback: For items where bot price ≤ stable (unpopular), use the standard trade multiplier as divisor. This ensures instant sell is always significantly below player price — it's meant to be a quick-cash discount, not a competitive offer.
Final Prices Calculator

Adjustment values are pulled automatically from the calculators above. Each price card has its own inputs.

Bot Trade Price
floor(baseBotTradePrice * (1 + botPriceChange) * (1 + tradePriceMarkup))
Player Trade Price
floor(basePlayerTradePrice * (1 + playerPriceChange))
if decreaseDepositForCheap = ON: max(result - cheapItemAdj, 1)
Store Price
Normal: max(round(stablePrice * (1+byDeficitBot) * (1+byAvg7DBot) * (1+storePriceMarkup)), minPrice)
Liquidation: if botStock>20 AND maxDeposit<-30 AND minPrice close to stablePrice:
  max(minPrice - max(round(minPrice*0.01), 1), 1)
Instant Sell Price
pricingMultiplier = baseBotTradePrice / max(stablePrice, 1)
divisor = pricingMultiplier > 1 ? pricingMultiplier : 1.75
round(finalPlayerPrice / divisor * (instantSellAdj% / 100))

10. Post-Calculation Protections

Safety nets that run after all price calculations. They catch edge cases the formulas miss: Doppler knives with unreliable market data, price changes too small to matter (avoids constant micro-fluctuations), and the hard rule that player price must never exceed bot price (or we'd lose money on every trade).

Even after all the math, we run 3 safety checks before publishing prices. Think of them as quality control on the assembly line:

  1. Doppler check: Doppler knives have wildly variable prices (Phase 1 vs Phase 4 can be 3x different). Special capping rules ensure we don't overpay based on wrong phase data.
  2. Stability filter: If the new price is less than 1% different from the old price, keep the old one. This prevents prices from flickering by tiny amounts every cycle.
  3. Margin guard: Player price must be ≤ 97% of bot price. If not, we'd lose money: paying users more than we can sell to bots for.

Protection chain (runs in order):

Calculated Prices
Doppler?
Change < 1%?
Player ≤ Bot*0.97?
Final Prices
Running Example

AK-47 is not a Doppler → skip. New bot price 2,461 vs previous ~2,450 → change is 0.4% < 1% → keep old price. Player 1,332 ≤ 2,461*0.97 (2,387)? Yes → OK. The 1% filter is the most commonly triggered protection — it keeps prices stable between runs.

1. Doppler Protection DOPPLER

Only for isDoppler = true:

2. Minor Change Dampening STABILITY

If price changed < 1% from previous run: keep old price. Applies to bot, player, and store separately.

if abs((new - old) / (old || 1)) * 100 < 1: keep old price
<1% dampening: Prices that flicker by 0.3% every cycle confuse users, generate unnecessary database writes, and make the platform feel unstable. Only publish a new price if it changed meaningfully.

3. Player/Bot Diff SAFETY

finalPlayer = min(finalPlayer, finalBot * 0.97)
3% minimum gap: If player price approaches or exceeds bot price, we'd lose money on every trade (paying users more than bots pay us). The 3% buffer accounts for fees and ensures minimum profitability.

Execution Order

Bot+Player: protectDopplers → protectMinorChanges → protectPlayerBotDiff

Store: protectStorePriceMinorChanges (separate chain)

11. Admin Configuration

Runtime-configurable flags and multipliers that control the aggressiveness of various adjustments. These can be changed in the admin panel without redeploying code, allowing quick response to market conditions.

These are the knobs operators can turn without touching code. They fall into 3 categories:

In a market crash, an operator might turn on the safety switches and reduce markups. During stable periods, they can relax the dials to be more competitive.

KeyTypeEffectUsed In
tradePriceMarkup"0.05"Markup on bot trade pricesFinal bot price
storePriceMarkup"0.05"Markup on store pricesStore price
instantSellPriceAdjustPercent"85"% of base for instant sellInstant sell
blockDepositByPrevMonthPrice"1"/"0"Full block on prev-month anomalyAdj #8
adjustByLiveToStablePriceRatio"1"/"0"Live-to-stable crash protectionAdj #7
blockDepositByAvg7DStablePrice"1"/"0"Spike deposit blockingAdj #9
liveToStableModifier"0.5"Aggressiveness of stable-to-min adjAdj #7B
avg7DStableBotPriceModifier"0.5"Aggressiveness of 7d bot boostAdj #7 bot
decreaseDepositPriceForCheapItems"1"/"0"Cent deduction for cheap itemsFinal player
weeklyInOutRatioWeight"0.5"Weekly vs monthly weightStock prot #5
wantedMaxStockLowCapacityRatio"0.3"Cheap item stock cap multiplierStock prot #3

12. Design Philosophy

If you take away one thing from this document, it's this: the system is designed to be paranoid about overpaying and aggressive about acquiring. It uses multiple overlapping safety nets at different time horizons, trusts Buff as the closest thing to ground truth, and always prefers action (reduce price, block deposits) over inaction when a signal looks bad. The philosophy is: we can always re-open deposits tomorrow, but we can't un-buy an overpriced item.

1. Conservative on buying (player price): Takes the MOST aggressive reduction across all 8 signals. One red flag is enough.
2. Aggressive on acquiring (bot price): Takes the MOST aggressive increase across 3 signals.
3. Liquidation over holding: Overstock + reasonable market = undercut to sell fast.
4. Floors prevent catastrophe: Store never below minPrice. Minor changes dampened. Player capped at 97% of bot.
5. Buff as ground truth: Most reliable signal for expensive items. Missing data = protective action.
6. Cheap vs expensive diverge: Expensive: %-based with scaling. Cheap: cent-based deductions.
7. Multiple time horizons: Daily, weekly, monthly, 7d avg, 30d median, prev month — each catches different anomalies.
8. Market share awareness: Stock targets respect our share of total market (30-50% caps).

Generated from pricing-manager source code. All formulas match the TypeScript implementation.